defmodule MvWeb.AuthControllerTest do use MvWeb.ConnCase, async: true import Phoenix.LiveViewTest import Phoenix.ConnTest # Helper to create an unauthenticated conn (preserves sandbox metadata) defp build_unauthenticated_conn(authenticated_conn) do # Create new conn but preserve sandbox metadata for database access new_conn = build_conn() # Copy sandbox metadata from authenticated conn if authenticated_conn.private[:ecto_sandbox] do Plug.Conn.put_private(new_conn, :ecto_sandbox, authenticated_conn.private[:ecto_sandbox]) else new_conn end end # Basic UI tests test "GET /sign-in shows sign in form", %{conn: authenticated_conn} do # Create unauthenticated conn for this test conn = build_unauthenticated_conn(authenticated_conn) conn = get(conn, ~p"/sign-in") assert html_response(conn, 200) =~ "Sign in" end test "GET /sign-out redirects to home", %{conn: authenticated_conn} do conn = conn_with_oidc_user(authenticated_conn) conn = get(conn, ~p"/sign-out") assert redirected_to(conn) == ~p"/" end # Password authentication (LiveView) test "password user can sign in with valid credentials via LiveView", %{ conn: authenticated_conn } do # Create unauthenticated conn for this test conn = build_unauthenticated_conn(authenticated_conn) _user = create_test_user(%{ email: "password@example.com", password: "secret123", oidc_id: nil }) {:ok, view, _html} = live(conn, "/sign-in") {:error, {:redirect, %{to: to}}} = view |> form("#user-password-sign-in-with-password", user: %{email: "password@example.com", password: "secret123"} ) |> render_submit() assert to =~ "/auth/user/password/sign_in_with_token" end test "password user with invalid credentials shows error via LiveView", %{ conn: authenticated_conn } do # Create unauthenticated conn for this test conn = build_unauthenticated_conn(authenticated_conn) _user = create_test_user(%{ email: "test@example.com", password: "correct_password", oidc_id: nil }) {:ok, view, _html} = live(conn, "/sign-in") html = view |> form("#user-password-sign-in-with-password", user: %{email: "test@example.com", password: "wrong_password"} ) |> render_submit() assert html =~ "Email or password was incorrect" end test "password user with non-existent email shows error via LiveView", %{ conn: authenticated_conn } do # Create unauthenticated conn for this test conn = build_unauthenticated_conn(authenticated_conn) {:ok, view, _html} = live(conn, "/sign-in") html = view |> form("#user-password-sign-in-with-password", user: %{email: "nonexistent@example.com", password: "anypassword"} ) |> render_submit() assert html =~ "Email or password was incorrect" end # Registration (LiveView) test "user can register with valid credentials via LiveView", %{conn: authenticated_conn} do # Create unauthenticated conn for this test conn = build_unauthenticated_conn(authenticated_conn) {:ok, view, _html} = live(conn, "/register") {:error, {:redirect, %{to: to}}} = view |> form("#user-password-register-with-password-wrapper form", user: %{email: "newuser@example.com", password: "newpassword123"} ) |> render_submit() assert to =~ "/auth/user/password/sign_in_with_token" end test "registration with existing email shows error via LiveView", %{conn: authenticated_conn} do # Create unauthenticated conn for this test conn = build_unauthenticated_conn(authenticated_conn) _user = create_test_user(%{ email: "existing@example.com", password: "secret123", oidc_id: nil }) {:ok, view, _html} = live(conn, "/register") html = view |> form("#user-password-register-with-password-wrapper form", user: %{email: "existing@example.com", password: "anotherpassword"} ) |> render_submit() assert html =~ "has already been taken" end test "registration with weak password shows error via LiveView", %{conn: authenticated_conn} do # Create unauthenticated conn for this test conn = build_unauthenticated_conn(authenticated_conn) {:ok, view, _html} = live(conn, "/register") html = view |> form("#user-password-register-with-password-wrapper form", user: %{email: "weakpass@example.com", password: "123"} ) |> render_submit() assert html =~ "length must be greater than or equal to 8" end # Access control test "unauthenticated user accessing protected route gets redirected to sign-in", %{ conn: authenticated_conn } do # Create unauthenticated conn for this test conn = build_unauthenticated_conn(authenticated_conn) conn = get(conn, ~p"/members") assert redirected_to(conn) == ~p"/sign-in" end test "authenticated user can access protected route", %{conn: authenticated_conn} do conn = conn_with_oidc_user(authenticated_conn) conn = get(conn, ~p"/members") assert conn.status == 200 end test "password authenticated user can access protected route via LiveView", %{ conn: authenticated_conn } do # Create unauthenticated conn for this test conn = build_unauthenticated_conn(authenticated_conn) _user = create_test_user(%{ email: "auth@example.com", password: "secret123", oidc_id: nil }) {:ok, view, _html} = live(conn, "/sign-in") {:error, {:redirect, %{to: to}}} = view |> form("#user-password-sign-in-with-password", user: %{email: "auth@example.com", password: "secret123"} ) |> render_submit() assert to =~ "/auth/user/password/sign_in_with_token" # After login, user is redirected to /auth/user/password/sign_in_with_token. Session handling for protected routes should be tested in integration or E2E tests. end # Edge cases test "user with nil oidc_id can still sign in with password via LiveView", %{ conn: authenticated_conn } do # Create unauthenticated conn for this test conn = build_unauthenticated_conn(authenticated_conn) _user = create_test_user(%{ email: "nil_oidc@example.com", password: "secret123", oidc_id: nil }) {:ok, view, _html} = live(conn, "/sign-in") {:error, {:redirect, %{to: to}}} = view |> form("#user-password-sign-in-with-password", user: %{email: "nil_oidc@example.com", password: "secret123"} ) |> render_submit() assert to =~ "/auth/user/password/sign_in_with_token" end test "user with empty string oidc_id is handled correctly via LiveView", %{ conn: authenticated_conn } do # Create unauthenticated conn for this test conn = build_unauthenticated_conn(authenticated_conn) _user = create_test_user(%{ email: "empty_oidc@example.com", password: "secret123", oidc_id: "" }) {:ok, view, _html} = live(conn, "/sign-in") {:error, {:redirect, %{to: to}}} = view |> form("#user-password-sign-in-with-password", user: %{email: "empty_oidc@example.com", password: "secret123"} ) |> render_submit() assert to =~ "/auth/user/password/sign_in_with_token" end end