user settings updated to lv and lv tests created. all tests working

This commit is contained in:
Adam Piontek 2021-03-02 16:48:00 -05:00
parent 24502e2667
commit ef7b8e0bb8
26 changed files with 389 additions and 288 deletions

View file

@ -172,10 +172,10 @@ defmodule Bones73k.Accounts do
{:error, %Ecto.Changeset{}} {:error, %Ecto.Changeset{}}
""" """
def apply_user_email(user, password, attrs) do def apply_user_email(user, %{"current_password" => curr_pw} = attrs) do
user user
|> User.email_changeset(attrs) |> User.email_changeset(attrs)
|> User.validate_current_password(password) |> User.validate_current_password(curr_pw)
|> Ecto.Changeset.apply_action(:update) |> Ecto.Changeset.apply_action(:update)
end end
@ -247,11 +247,11 @@ defmodule Bones73k.Accounts do
{:error, %Ecto.Changeset{}} {:error, %Ecto.Changeset{}}
""" """
def update_user_password(user, password, attrs) do def update_user_password(user, %{"current_password" => curr_pw} = attrs) do
changeset = changeset =
user user
|> User.password_changeset(attrs) |> User.password_changeset(attrs)
|> User.validate_current_password(password) |> User.validate_current_password(curr_pw)
Ecto.Multi.new() Ecto.Multi.new()
|> Ecto.Multi.update(:user, changeset) |> Ecto.Multi.update(:user, changeset)

View file

@ -35,10 +35,6 @@ defmodule Bones73kWeb.UserAuth do
|> put_session(:user_token, token) |> put_session(:user_token, token)
|> put_session(:live_socket_id, "users_sessions:#{Base.url_encode64(token)}") |> put_session(:live_socket_id, "users_sessions:#{Base.url_encode64(token)}")
|> maybe_write_remember_me_cookie(token, params) |> maybe_write_remember_me_cookie(token, params)
|> put_flash(
:info,
raw("Welcome back, #{user.email} — you were logged in successfuly.")
)
|> redirect(to: get_session(conn, :user_return_to) || signed_in_path(conn)) |> redirect(to: get_session(conn, :user_return_to) || signed_in_path(conn))
end end

View file

@ -1,6 +1,7 @@
defmodule Bones73kWeb.UserSessionController do defmodule Bones73kWeb.UserSessionController do
use Bones73kWeb, :controller use Bones73kWeb, :controller
alias Phoenix.HTML
alias Bones73k.Accounts alias Bones73k.Accounts
alias Bones73k.Accounts.User alias Bones73k.Accounts.User
alias Bones73kWeb.UserAuth alias Bones73kWeb.UserAuth
@ -11,7 +12,12 @@ defmodule Bones73kWeb.UserSessionController do
def create(conn, %{"user" => %{"email" => email, "password" => password} = user_params}) do def create(conn, %{"user" => %{"email" => email, "password" => password} = user_params}) do
if user = Accounts.get_user_by_email_and_password(email, password) do if user = Accounts.get_user_by_email_and_password(email, password) do
UserAuth.log_in_user(conn, user, user_params) conn
|> put_flash(
:info,
HTML.raw("Welcome back, #{user.email} — you were logged in successfuly.")
)
|> UserAuth.log_in_user(user, user_params)
else else
render(conn, "new.html", error_message: "Invalid email or password") render(conn, "new.html", error_message: "Invalid email or password")
end end

View file

@ -2,36 +2,6 @@ defmodule Bones73kWeb.UserSettingsController do
use Bones73kWeb, :controller use Bones73kWeb, :controller
alias Bones73k.Accounts alias Bones73k.Accounts
alias Bones73kWeb.UserAuth
plug(:assign_email_and_password_changesets)
def edit(conn, _params) do
render(conn, "edit.html")
end
def update_email(conn, %{"current_password" => password, "user" => user_params}) do
user = conn.assigns.current_user
case Accounts.apply_user_email(user, password, user_params) do
{:ok, applied_user} ->
Accounts.deliver_update_email_instructions(
applied_user,
user.email,
&Routes.user_settings_url(conn, :confirm_email, &1)
)
conn
|> put_flash(
:info,
"A link to confirm your email change has been sent to the new address."
)
|> redirect(to: Routes.user_settings_path(conn, :edit))
{:error, changeset} ->
render(conn, "edit.html", email_changeset: changeset)
end
end
def confirm_email(conn, %{"token" => token}) do def confirm_email(conn, %{"token" => token}) do
case Accounts.update_user_email(conn.assigns.current_user, token) do case Accounts.update_user_email(conn.assigns.current_user, token) do
@ -46,27 +16,4 @@ defmodule Bones73kWeb.UserSettingsController do
|> redirect(to: Routes.user_settings_path(conn, :edit)) |> redirect(to: Routes.user_settings_path(conn, :edit))
end end
end end
def update_password(conn, %{"current_password" => password, "user" => user_params}) do
user = conn.assigns.current_user
case Accounts.update_user_password(user, password, user_params) do
{:ok, user} ->
conn
|> put_flash(:info, "Password updated successfully.")
|> put_session(:user_return_to, Routes.user_settings_path(conn, :edit))
|> UserAuth.log_in_user(user)
{:error, changeset} ->
render(conn, "edit.html", password_changeset: changeset)
end
end
defp assign_email_and_password_changesets(conn, _opts) do
user = conn.assigns.current_user
conn
|> assign(:email_changeset, Accounts.change_user_email(user))
|> assign(:password_changeset, Accounts.change_user_password(user))
end
end end

View file

@ -4,12 +4,6 @@ defmodule Bones73kWeb.UserLive.Registration do
alias Bones73k.Accounts alias Bones73k.Accounts
alias Bones73k.Accounts.User alias Bones73k.Accounts.User
@messages [
success: "Welcome! New accout created.",
info:
"Some features may be unavailable until you confirm your email. Check your inbox for instructions."
]
@impl true @impl true
def mount(_params, session, socket) do def mount(_params, session, socket) do
socket socket
@ -25,7 +19,11 @@ defmodule Bones73kWeb.UserLive.Registration do
%{ %{
user_id: nil, user_id: nil,
user_return_to: Map.get(session, "user_return_to", "/"), user_return_to: Map.get(session, "user_return_to", "/"),
messages: @messages messages: [
success: "Welcome! Your new account has been created, and you've been logged in.",
info:
"Some features may be unavailable until you confirm your email address. Check your inbox for instructions."
]
} }
end end

View file

@ -27,7 +27,7 @@ defmodule Bones73kWeb.UserLive.ResetPassword do
{:ok, _} -> {:ok, _} ->
{:noreply, {:noreply,
socket socket
|> put_flash(:success, "Password reset successfully.") |> put_flash(:info, "Password reset successfully.")
|> redirect(to: Routes.user_session_path(socket, :new))} |> redirect(to: Routes.user_session_path(socket, :new))}
{:error, changeset} -> {:error, changeset} ->

View file

@ -0,0 +1,39 @@
defmodule Bones73kWeb.UserLive.Settings do
use Bones73kWeb, :live_view
alias Bones73k.Accounts.User
@impl true
def mount(_params, session, socket) do
socket
|> assign_defaults(session)
|> alert_email_verified?()
|> live_okreply()
end
defp alert_email_verified?(socket) do
case socket.assigns.current_user do
%{confirmed_at: nil} ->
put_flash(socket, :warning, [
"Your email hasn't been confirmed, some areas may be restricted. Shall we ",
link("resend the verification email?",
to: Routes.user_confirmation_path(socket, :new),
class: "alert-link"
)
])
_ ->
socket
end
end
@impl true
def handle_info({:put_flash_message, {flash_type, msg}}, socket) do
socket |> put_flash(flash_type, msg) |> live_noreply()
end
@impl true
def handle_info({:clear_flash_message, flash_type}, socket) do
socket |> clear_flash(flash_type) |> live_noreply()
end
end

View file

@ -0,0 +1,9 @@
<h3 class="mb-3">
<%= icon_div @socket, "bi-sliders", [class: "icon baseline"] %>
User Settings
</h3>
<div class="row">
<%= live_component @socket, Bones73kWeb.UserLive.Settings.Email, id: "email-#{@current_user.id}", current_user: @current_user %>
<%= live_component @socket, Bones73kWeb.UserLive.Settings.Password, id: "password-#{@current_user.id}", current_user: @current_user %>
</div>

View file

@ -0,0 +1,62 @@
defmodule Bones73kWeb.UserLive.Settings.Email do
use Bones73kWeb, :live_component
alias Bones73k.Accounts
alias Bones73k.Accounts.User
@impl true
def update(%{current_user: user} = assigns, socket) do
socket
|> assign(id: assigns.id)
|> assign(current_user: user)
|> assign(changeset: get_changeset(user))
|> live_okreply()
end
defp get_changeset(user, user_params \\ %{}) do
Accounts.change_user_email(user, user_params)
end
@impl true
def handle_event("validate", %{"user" => user_params}, socket) do
cs = get_changeset(socket.assigns.current_user, user_params)
{:noreply, assign(socket, changeset: %{cs | action: :update})}
end
# user_settings_path GET /users/settings/confirm_email/:token Bones73kWeb.UserSettingsController :confirm_email
@impl true
def handle_event("save", %{"user" => user_params}, socket) do
case Accounts.apply_user_email(socket.assigns.current_user, user_params) do
{:ok, applied_user} ->
Accounts.deliver_update_email_instructions(
applied_user,
socket.assigns.current_user.email,
&Routes.user_settings_url(socket, :confirm_email, &1)
)
send(self(), {:clear_flash_message, :error})
send(
self(),
{:put_flash_message,
{:info, "A link to confirm your e-mail change has been sent to the new address."}}
)
socket
|> assign(changeset: get_changeset(socket.assigns.current_user))
|> live_noreply()
{:error, cs} ->
cu = socket.assigns.current_user
cpw = user_params["current_password"]
valid_password? = User.valid_password?(cu, cpw)
msg = (valid_password? && "Could not reset email.") || "Invalid current password."
send(self(), {:put_flash_message, {:error, msg}})
socket
|> assign(changeset: cs)
|> live_noreply()
end
end
end

View file

@ -0,0 +1,51 @@
<div id="<%= @id %>" class="col-sm-9 col-md-7 col-lg-5 col-xl-4 mt-1">
<h4>Change email</h4>
<%= form_for @changeset, "#", [phx_change: :validate, phx_submit: :save, phx_target: @myself], fn f -> %>
<div class="mb-3" phx-feedback-for="<%= input_id(f, :email) %>">
<%= label f, :email, class: "form-label" %>
<div class="input-group has-validation">
<span class="input-group-text">
<%= icon_div @socket, "bi-at", [class: "icon fs-5"] %>
</span>
<%= email_input f, :email,
value: input_value(f, :email),
class: input_class(f, :email, "form-control"),
placeholder: "e.g., babka@73k.us",
maxlength: User.max_email,
phx_debounce: "600",
aria_describedby: error_id(f, :email)
%>
<%= error_tag f, :email %>
</div>
</div>
<div class="mb-3" phx-feedback-for="<%= input_id(f, :current_password) %>">
<%= label f, :current_password, class: "form-label" %>
<div class="input-group">
<span class="input-group-text">
<%= icon_div @socket, "bi-lock", [class: "icon fs-5"] %>
</span>
<%= password_input f, :current_password,
class: "form-control",
required: true,
aria_describedby: error_id(f, :current_password)
%>
<%= error_tag f, :current_password %>
</div>
</div>
<div class="mb-3">
<%= submit "Change email",
disabled: !@changeset.valid? || input_value(f, :current_password) == "",
class: "btn btn-primary",
phx_disable_with: "Saving..."
%>
</div>
<% end %>
</div>

View file

@ -0,0 +1,57 @@
defmodule Bones73kWeb.UserLive.Settings.Password do
use Bones73kWeb, :live_component
alias Bones73k.Accounts
alias Bones73k.Accounts.User
@impl true
def update(%{current_user: user} = assigns, socket) do
socket
|> assign(id: assigns.id)
|> assign(current_user: user)
|> assign(changeset: get_changeset(user))
|> assign(login_params: init_login_params(socket))
|> assign(trigger_submit: false)
|> live_okreply()
end
defp get_changeset(user, user_params \\ %{}) do
Accounts.change_user_password(user, user_params)
end
defp init_login_params(socket) do
%{
user_id: nil,
user_return_to: Routes.user_settings_path(socket, :edit),
messages: [info: "Password updated successfully."]
}
end
@impl true
def handle_event("validate", %{"user" => user_params}, socket) do
cs = get_changeset(socket.assigns.current_user, user_params)
{:noreply, assign(socket, changeset: %{cs | action: :update})}
end
@impl true
def handle_event("save", %{"user" => user_params}, socket) do
case Accounts.update_user_password(socket.assigns.current_user, user_params) do
{:ok, user} ->
socket
|> assign(login_params: %{socket.assigns.login_params | user_id: user.id})
|> assign(trigger_submit: true)
|> live_noreply()
{:error, cs} ->
cu = socket.assigns.current_user
cpw = user_params["current_password"]
valid_password? = User.valid_password?(cu, cpw)
msg = (valid_password? && "Could not change password.") || "Invalid current password."
send(self(), {:put_flash_message, {:error, msg}})
socket
|> assign(changeset: cs)
|> live_noreply()
end
end
end

View file

@ -0,0 +1,76 @@
<div id="<%= @id %>" class="col-sm-9 col-md-7 col-lg-5 col-xl-4 mt-1">
<h4>Change password</h4>
<%= form_for @changeset, "#", [phx_change: :validate, phx_submit: :save, phx_target: @myself], fn f -> %>
<div class="mb-3" phx-feedback-for="<%= input_id(f, :password) %>">
<%= label f, :password, "New password", class: "form-label" %>
<div class="input-group has-validation">
<span class="input-group-text">
<%= icon_div @socket, "bi-key", [class: "icon fs-5"] %>
</span>
<%= password_input f, :password,
value: input_value(f, :password),
class: input_class(f, :password, "form-control"),
maxlength: User.max_password,
phx_debounce: "600",
aria_describedby: error_id(f, :password)
%>
<%= error_tag f, :password %>
</div>
</div>
<div class="mb-3" phx-feedback-for="<%= input_id(f, :password_confirmation) %>">
<%= label f, :password_confirmation, "Confirm new password", class: "form-label" %>
<div class="input-group has-validation">
<span class="input-group-text">
<%= icon_div @socket, "bi-key-fill", [class: "icon fs-5"] %>
</span>
<%= password_input f, :password_confirmation,
value: input_value(f, :password_confirmation),
class: input_class(f, :password_confirmation, "form-control"),
maxlength: User.max_password,
aria_describedby: error_id(f, :password_confirmation)
%>
<%= error_tag f, :password_confirmation %>
</div>
</div>
<div class="mb-3" phx-feedback-for="<%= input_id(f, :current_password) %>">
<%= label f, :current_password, class: "form-label" %>
<div class="input-group">
<span class="input-group-text">
<%= icon_div @socket, "bi-lock", [class: "icon fs-5"] %>
</span>
<%= password_input f, :current_password,
class: "form-control",
required: true,
aria_describedby: error_id(f, :current_password)
%>
<%= error_tag f, :current_password %>
</div>
</div>
<div class="mb-3">
<%= submit "Change password",
disabled: !@changeset.valid? || input_value(f, :current_password) == "",
class: "btn btn-primary",
phx_disable_with: "Saving..."
%>
</div>
<% end %>
<%# hidden form for initial login after registration %>
<%= form_for :user, Routes.user_session_path(@socket, :create), [phx_trigger_action: @trigger_submit, id: "settings_pw_change_trigger"], fn f -> %>
<%= hidden_input f, :params_token, value: Phoenix.Token.encrypt(Bones73kWeb.Endpoint, "login_params", @login_params) %>
<% end %>
<%# hidden form to submit user for relogin after password change %>
<%#= form_for :user_login, Routes.user_session_path(@socket, :create), [phx_trigger_action: @trigger_submit], fn f -> %>
<%#= hidden_input f, :login_params_token, value: Phoenix.Token.encrypt(Bones73kWeb.Endpoint, "login_params", @login_params) %>
<%#= hidden_input f, :remember_me, value: false %>
<%# end %>
</div>

View file

@ -72,13 +72,9 @@ defmodule Bones73kWeb.Router do
pipe_through([:browser, :require_authenticated_user]) pipe_through([:browser, :require_authenticated_user])
# # liveview user settings # # liveview user settings
# live "/users/settings", UserLive.Settings, :edit live "/users/settings", UserLive.Settings, :edit
# original user routes from phx.gen.auth # original user routes from phx.gen.auth
# TODO:
get("/users/settings", UserSettingsController, :edit)
put("/users/settings/update_password", UserSettingsController, :update_password)
put("/users/settings/update_email", UserSettingsController, :update_email)
get("/users/settings/confirm_email/:token", UserSettingsController, :confirm_email) get("/users/settings/confirm_email/:token", UserSettingsController, :confirm_email)
end end
@ -86,7 +82,6 @@ defmodule Bones73kWeb.Router do
pipe_through([:browser]) pipe_through([:browser])
delete("/users/log_out", UserSessionController, :delete) delete("/users/log_out", UserSessionController, :delete)
# TODO: understanding/testing force_logout?
get("/users/force_logout", UserSessionController, :force_logout) get("/users/force_logout", UserSessionController, :force_logout)
get("/users/confirm", UserConfirmationController, :new) get("/users/confirm", UserConfirmationController, :new)
post("/users/confirm", UserConfirmationController, :create) post("/users/confirm", UserConfirmationController, :create)

View file

@ -2,7 +2,7 @@
<%# phoenix flash alerts: %> <%# phoenix flash alerts: %>
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-11 col-lg-9 col-xl-8 "> <div class="col-md-12 col-lg-10 col-xl-9 ">
<%= for {kind, color} <- alert_kinds() do %> <%= for {kind, color} <- alert_kinds() do %>
<%= if flash_content = get_flash(@conn, kind) do %> <%= if flash_content = get_flash(@conn, kind) do %>
<div class="alert alert-<%= color %> alert-dismissible fade show" role="alert"> <div class="alert alert-<%= color %> alert-dismissible fade show" role="alert">

View file

@ -2,7 +2,7 @@
<%# liveview flash alerts: %> <%# liveview flash alerts: %>
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-11 col-lg-9 col-xl-8 "> <div class="col-md-12 col-lg-10 col-xl-9 ">
<%= for {kind, color} <- alert_kinds() do %> <%= for {kind, color} <- alert_kinds() do %>
<%= if flash_content = live_flash(@flash, kind) do %> <%= if flash_content = live_flash(@flash, kind) do %>
<div class="alert alert-<%= color %> alert-dismissible fade show" role="alert" id="lv-alert-<%= kind %>" phx-hook="AlertRemover" data-key="<%= kind %>"> <div class="alert alert-<%= color %> alert-dismissible fade show" role="alert" id="lv-alert-<%= kind %>" phx-hook="AlertRemover" data-key="<%= kind %>">

View file

@ -1,49 +0,0 @@
<h1>Settings</h1>
<h3>Change email</h3>
<%= form_for @email_changeset, Routes.user_settings_path(@conn, :update_email), fn f -> %>
<%= if @email_changeset.action do %>
<div class="alert alert-danger">
<p>Oops, something went wrong! Please check the errors below.</p>
</div>
<% end %>
<%= label f, :email %>
<%= email_input f, :email, required: true %>
<%= error_tag f, :email %>
<%= label f, :current_password, for: "current_password_for_email" %>
<%= password_input f, :current_password, required: true, name: "current_password", id: "current_password_for_email" %>
<%= error_tag f, :current_password %>
<div>
<%= submit "Change email" %>
</div>
<% end %>
<h3>Change password</h3>
<%= form_for @password_changeset, Routes.user_settings_path(@conn, :update_password), fn f -> %>
<%= if @password_changeset.action do %>
<div class="alert alert-danger">
<p>Oops, something went wrong! Please check the errors below.</p>
</div>
<% end %>
<%= label f, :password, "New password" %>
<%= password_input f, :password, required: true %>
<%= error_tag f, :password %>
<%= label f, :password_confirmation, "Confirm new password" %>
<%= password_input f, :password_confirmation, required: true %>
<%= error_tag f, :password_confirmation %>
<%= label f, :current_password, for: "current_password_for_password" %>
<%= password_input f, :current_password, required: true, name: "current_password", id: "current_password_for_password" %>
<%= error_tag f, :current_password %>
<div>
<%= submit "Change password" %>
</div>
<% end %>

View file

@ -1,3 +0,0 @@
defmodule Bones73kWeb.UserSettingsView do
use Bones73kWeb, :view
end

View file

@ -58,19 +58,19 @@ defmodule Bones73k.AccountsTest do
end end
test "validates email and password when given" do test "validates email and password when given" do
{:error, changeset} = Accounts.register_user(%{email: "not valid", password: "not valid"}) {:error, changeset} = Accounts.register_user(%{email: "not valid", password: "2shrt"})
pw_err = "should be at least #{User.min_password()} character(s)"
assert %{ assert "must be a valid email address" in errors_on(changeset).email
email: ["must have the @ sign and no spaces"], assert pw_err in errors_on(changeset).password
password: ["should be at least 12 character(s)"]
} = errors_on(changeset)
end end
test "validates maximum values for email and password for security" do test "validates maximum values for email and password for security" do
too_long = String.duplicate("db", 100) too_long = "#{String.duplicate("db", 300)}@example.com"
{:error, changeset} = Accounts.register_user(%{email: too_long, password: too_long}) {:error, changeset} = Accounts.register_user(%{email: too_long, password: too_long})
assert "should be at most 160 character(s)" in errors_on(changeset).email em_err = "should be at most #{User.max_email()} character(s)"
assert "should be at most 80 character(s)" in errors_on(changeset).password pw_err = "should be at most #{User.max_password()} character(s)"
assert em_err in errors_on(changeset).email
assert pw_err in errors_on(changeset).password
end end
test "validates email uniqueness" do test "validates email uniqueness" do
@ -92,16 +92,22 @@ defmodule Bones73k.AccountsTest do
assert is_nil(user.password) assert is_nil(user.password)
assert user.role == :user assert user.role == :user
end end
end
describe "register_admin/1" do test "registers different role :manager and sets role to :manager" do
test "registers users with a hashed password and sets role to :admin" do
email = unique_user_email() email = unique_user_email()
{:ok, user} = Accounts.register_admin(%{email: email, password: valid_user_password()}) attrs = %{email: email, role: :manager, password: valid_user_password()}
{:ok, user} = Accounts.register_user(attrs)
assert user.email == email assert user.email == email
assert is_binary(user.hashed_password) assert is_binary(user.hashed_password)
assert is_nil(user.confirmed_at) assert is_nil(user.confirmed_at)
assert is_nil(user.password) assert is_nil(user.password)
assert user.role == :manager
end
test "registers different role :admin and sets role to :admin" do
email = unique_user_email()
attrs = %{email: email, role: :admin, password: valid_user_password()}
{:ok, user} = Accounts.register_user(attrs)
assert user.role == :admin assert user.role == :admin
end end
end end
@ -109,7 +115,7 @@ defmodule Bones73k.AccountsTest do
describe "change_user_registration/2" do describe "change_user_registration/2" do
test "returns a changeset" do test "returns a changeset" do
assert %Ecto.Changeset{} = changeset = Accounts.change_user_registration(%User{}) assert %Ecto.Changeset{} = changeset = Accounts.change_user_registration(%User{})
assert changeset.required == [:password, :email] assert changeset.required == [:password, :email, :role]
end end
end end
@ -126,45 +132,42 @@ defmodule Bones73k.AccountsTest do
end end
test "requires email to change", %{user: user} do test "requires email to change", %{user: user} do
{:error, changeset} = Accounts.apply_user_email(user, valid_user_password(), %{}) attrs = %{"current_password" => valid_user_password()}
{:error, changeset} = Accounts.apply_user_email(user, attrs)
assert %{email: ["did not change"]} = errors_on(changeset) assert %{email: ["did not change"]} = errors_on(changeset)
end end
test "validates email", %{user: user} do test "validates email", %{user: user} do
{:error, changeset} = attrs = %{"current_password" => valid_user_password(), "email" => "not valid"}
Accounts.apply_user_email(user, valid_user_password(), %{email: "not valid"}) {:error, changeset} = Accounts.apply_user_email(user, attrs)
assert %{email: ["must be a valid email address"]} = errors_on(changeset)
assert %{email: ["must have the @ sign and no spaces"]} = errors_on(changeset)
end end
test "validates maximum value for email for security", %{user: user} do test "validates maximum value for email for security", %{user: user} do
too_long = String.duplicate("db", 100) too_long = "#{String.duplicate("db", 300)}@example.com"
attrs = %{"current_password" => valid_user_password(), "email" => too_long}
{:error, changeset} = {:error, changeset} = Accounts.apply_user_email(user, attrs)
Accounts.apply_user_email(user, valid_user_password(), %{email: too_long}) em_err = "should be at most #{User.max_email()} character(s)"
assert em_err in errors_on(changeset).email
assert "should be at most 160 character(s)" in errors_on(changeset).email
end end
test "validates email uniqueness", %{user: user} do test "validates email uniqueness", %{user: user} do
%{email: email} = user_fixture() %{email: email} = user_fixture()
attrs = %{"current_password" => valid_user_password(), "email" => email}
{:error, changeset} = {:error, changeset} = Accounts.apply_user_email(user, attrs)
Accounts.apply_user_email(user, valid_user_password(), %{email: email})
assert "has already been taken" in errors_on(changeset).email assert "has already been taken" in errors_on(changeset).email
end end
test "validates current password", %{user: user} do test "validates current password", %{user: user} do
{:error, changeset} = attrs = %{"current_password" => "invalid", "email" => unique_user_email()}
Accounts.apply_user_email(user, "invalid", %{email: unique_user_email()}) {:error, changeset} = Accounts.apply_user_email(user, attrs)
assert %{current_password: ["is not valid"]} = errors_on(changeset) assert %{current_password: ["is not valid"]} = errors_on(changeset)
end end
test "applies the email without persisting it", %{user: user} do test "applies the email without persisting it", %{user: user} do
email = unique_user_email() email = unique_user_email()
{:ok, user} = Accounts.apply_user_email(user, valid_user_password(), %{email: email}) attrs = %{"current_password" => valid_user_password(), "email" => email}
{:ok, user} = Accounts.apply_user_email(user, attrs)
assert user.email == email assert user.email == email
assert Accounts.get_user!(user.id).email != email assert Accounts.get_user!(user.id).email != email
end end
@ -245,52 +248,47 @@ defmodule Bones73k.AccountsTest do
end end
test "validates password", %{user: user} do test "validates password", %{user: user} do
{:error, changeset} = attrs = %{
Accounts.update_user_password(user, valid_user_password(), %{ "current_password" => valid_user_password(),
password: "not valid", "password" => "2shrt",
password_confirmation: "another" "password_confirmation" => "another"
}) }
assert %{ {:error, changeset} = Accounts.update_user_password(user, attrs)
password: ["should be at least 12 character(s)"], pw_err = "should be at least #{User.min_password()} character(s)"
password_confirmation: ["does not match password"] conf_err = "does not match password"
} = errors_on(changeset) assert pw_err in errors_on(changeset).password
assert conf_err in errors_on(changeset).password_confirmation
end end
test "validates maximum values for password for security", %{user: user} do test "validates maximum values for password for security", %{user: user} do
too_long = String.duplicate("db", 100) attrs = %{
"current_password" => valid_user_password(),
"password" => String.duplicate("db", 100)
}
{:error, changeset} = {:error, changeset} = Accounts.update_user_password(user, attrs)
Accounts.update_user_password(user, valid_user_password(), %{password: too_long}) pw_err = "should be at most #{User.max_password()} character(s)"
assert pw_err in errors_on(changeset).password
assert "should be at most 80 character(s)" in errors_on(changeset).password
end end
test "validates current password", %{user: user} do test "validates current password", %{user: user} do
{:error, changeset} = attrs = %{"current_password" => "invalid", "password" => valid_user_password()}
Accounts.update_user_password(user, "invalid", %{password: valid_user_password()}) {:error, changeset} = Accounts.update_user_password(user, attrs)
assert %{current_password: ["is not valid"]} = errors_on(changeset) assert %{current_password: ["is not valid"]} = errors_on(changeset)
end end
test "updates the password", %{user: user} do test "updates the password", %{user: user} do
{:ok, user} = attrs = %{"current_password" => valid_user_password(), "password" => "new valid password"}
Accounts.update_user_password(user, valid_user_password(), %{ {:ok, user} = Accounts.update_user_password(user, attrs)
password: "new valid password"
})
assert is_nil(user.password) assert is_nil(user.password)
assert Accounts.get_user_by_email_and_password(user.email, "new valid password") assert Accounts.get_user_by_email_and_password(user.email, "new valid password")
end end
test "deletes all tokens for the given user", %{user: user} do test "deletes all tokens for the given user", %{user: user} do
_ = Accounts.generate_user_session_token(user) _ = Accounts.generate_user_session_token(user)
attrs = %{"current_password" => valid_user_password(), "password" => "new valid password"}
{:ok, _} = {:ok, _} = Accounts.update_user_password(user, attrs)
Accounts.update_user_password(user, valid_user_password(), %{
password: "new valid password"
})
refute Repo.get_by(UserToken, user_id: user.id) refute Repo.get_by(UserToken, user_id: user.id)
end end
end end
@ -456,14 +454,13 @@ defmodule Bones73k.AccountsTest do
test "validates password", %{user: user} do test "validates password", %{user: user} do
{:error, changeset} = {:error, changeset} =
Accounts.reset_user_password(user, %{ Accounts.reset_user_password(user, %{
password: "not valid", password: "2shrt",
password_confirmation: "another" password_confirmation: "another"
}) })
assert %{ pw_err = "should be at least #{User.min_password()} character(s)"
password: ["should be at least 12 character(s)"], assert pw_err in errors_on(changeset).password
password_confirmation: ["does not match password"] assert "does not match password" in errors_on(changeset).password_confirmation
} = errors_on(changeset)
end end
test "validates maximum values for password for security", %{user: user} do test "validates maximum values for password for security", %{user: user} do

View file

@ -13,7 +13,8 @@ defmodule Bones73kWeb.UserRegistrationControllerTest do
end end
test "redirects if already logged in", %{conn: conn} do test "redirects if already logged in", %{conn: conn} do
conn = conn |> log_in_user(user_fixture()) |> get(Routes.user_registration_path(conn, :new)) to = Routes.user_registration_path(conn, :new)
conn = conn |> log_in_user(user_fixture()) |> get(to)
assert redirected_to(conn) == "/" assert redirected_to(conn) == "/"
end end
end end

View file

@ -15,6 +15,12 @@ defmodule Bones73kWeb.UserResetPasswordControllerTest do
response = html_response(conn, 200) response = html_response(conn, 200)
assert response =~ "Forgot your password?\n </h3>" assert response =~ "Forgot your password?\n </h3>"
end end
test "redirects if already logged in", %{conn: conn} do
to = Routes.user_reset_password_path(conn, :new)
conn = conn |> log_in_user(user_fixture()) |> get(to)
assert redirected_to(conn) == "/"
end
end end
describe "POST /users/reset_password" do describe "POST /users/reset_password" do

View file

@ -10,7 +10,7 @@ defmodule Bones73kWeb.UserSettingsControllerTest do
test "renders settings page", %{conn: conn} do test "renders settings page", %{conn: conn} do
conn = get(conn, Routes.user_settings_path(conn, :edit)) conn = get(conn, Routes.user_settings_path(conn, :edit))
response = html_response(conn, 200) response = html_response(conn, 200)
assert response =~ "<h1>Settings</h1>" assert response =~ "User Settings\n</h3>"
end end
test "redirects if user is not logged in" do test "redirects if user is not logged in" do
@ -20,71 +20,6 @@ defmodule Bones73kWeb.UserSettingsControllerTest do
end end
end end
describe "PUT /users/settings/update_password" do
test "updates the user password and resets tokens", %{conn: conn, user: user} do
new_password_conn =
put(conn, Routes.user_settings_path(conn, :update_password), %{
"current_password" => valid_user_password(),
"user" => %{
"password" => "new valid password",
"password_confirmation" => "new valid password"
}
})
assert redirected_to(new_password_conn) == Routes.user_settings_path(conn, :edit)
assert get_session(new_password_conn, :user_token) != get_session(conn, :user_token)
assert get_flash(new_password_conn, :info) =~ "Password updated successfully"
assert Accounts.get_user_by_email_and_password(user.email, "new valid password")
end
test "does not update password on invalid data", %{conn: conn} do
old_password_conn =
put(conn, Routes.user_settings_path(conn, :update_password), %{
"current_password" => "invalid",
"user" => %{
"password" => "too short",
"password_confirmation" => "does not match"
}
})
response = html_response(old_password_conn, 200)
assert response =~ "<h1>Settings</h1>"
assert response =~ "should be at least 12 character(s)"
assert response =~ "does not match password"
assert response =~ "is not valid"
assert get_session(old_password_conn, :user_token) == get_session(conn, :user_token)
end
end
describe "PUT /users/settings/update_email" do
@tag :capture_log
test "updates the user email", %{conn: conn, user: user} do
conn =
put(conn, Routes.user_settings_path(conn, :update_email), %{
"current_password" => valid_user_password(),
"user" => %{"email" => unique_user_email()}
})
assert redirected_to(conn) == Routes.user_settings_path(conn, :edit)
assert get_flash(conn, :info) =~ "A link to confirm your email"
assert Accounts.get_user_by_email(user.email)
end
test "does not update email on invalid data", %{conn: conn} do
conn =
put(conn, Routes.user_settings_path(conn, :update_email), %{
"current_password" => "invalid",
"user" => %{"email" => "with spaces"}
})
response = html_response(conn, 200)
assert response =~ "<h1>Settings</h1>"
assert response =~ "must have the @ sign and no spaces"
assert response =~ "is not valid"
end
end
describe "GET /users/settings/confirm_email/:token" do describe "GET /users/settings/confirm_email/:token" do
setup %{user: user} do setup %{user: user} do
email = unique_user_email() email = unique_user_email()

View file

@ -52,9 +52,6 @@ defmodule Bones73kWeb.AdminDashboardLiveTest do
assert "/" = redir_path = redirected_to(conn, 302) assert "/" = redir_path = redirected_to(conn, 302)
conn = get(recycle(conn), redir_path) conn = get(recycle(conn), redir_path)
assert "/users/log_in" = redir_path = redirected_to(conn, 302)
conn = get(recycle(conn), redir_path)
assert html_response(conn, 200) =~ assert html_response(conn, 200) =~
"You were logged out. Please login again to continue using our application." "You were logged out. Please login again to continue using our application."
end end

View file

@ -4,12 +4,6 @@ defmodule Bones73kWeb.PageLiveTest do
import Phoenix.LiveViewTest import Phoenix.LiveViewTest
import Bones73k.AccountsFixtures import Bones73k.AccountsFixtures
test "disconnected and connected render without authentication should redirect to login page",
%{conn: conn} do
# If we don't previously log in we will be redirected to the login page
assert {:error, {:redirect, %{to: "/users/log_in"}}} = live(conn, "/")
end
test "disconnected and connected render with authentication should redirect to index page", %{ test "disconnected and connected render with authentication should redirect to index page", %{
conn: conn conn: conn
} do } do
@ -44,9 +38,6 @@ defmodule Bones73kWeb.PageLiveTest do
assert "/" = redir_path = redirected_to(conn, 302) assert "/" = redir_path = redirected_to(conn, 302)
conn = get(recycle(conn), redir_path) conn = get(recycle(conn), redir_path)
assert "/users/log_in" = redir_path = redirected_to(conn, 302)
conn = get(recycle(conn), redir_path)
assert html_response(conn, 200) =~ assert html_response(conn, 200) =~
"You were logged out. Please login again to continue using our application." "You were logged out. Please login again to continue using our application."
end end

View file

@ -188,16 +188,13 @@ defmodule Bones73kWeb.PropertyLiveTest do
assert_receive {:DOWN, ^ref, _, _, _} assert_receive {:DOWN, ^ref, _, _, _}
refute Process.alive?(index_live.pid) refute Process.alive?(index_live.pid)
# Assert our liveview was redirected, following first to /users/force_logout, then to "/", and then to "/users/log_in" # Assert our liveview was redirected, following first to /users/force_logout, then to "/"
assert_redirect(index_live, "/users/force_logout") assert_redirect(index_live, "/users/force_logout")
conn = get(conn, "/users/force_logout") conn = get(conn, "/users/force_logout")
assert "/" = redir_path = redirected_to(conn, 302) assert "/" = redir_path = redirected_to(conn, 302)
conn = get(recycle(conn), redir_path) conn = get(recycle(conn), redir_path)
assert "/users/log_in" = redir_path = redirected_to(conn, 302)
conn = get(recycle(conn), redir_path)
assert html_response(conn, 200) =~ assert html_response(conn, 200) =~
"You were logged out. Please login again to continue using our application." "You were logged out. Please login again to continue using our application."
end end
@ -338,9 +335,6 @@ defmodule Bones73kWeb.PropertyLiveTest do
assert "/" = redir_path = redirected_to(conn, 302) assert "/" = redir_path = redirected_to(conn, 302)
conn = get(recycle(conn), redir_path) conn = get(recycle(conn), redir_path)
assert "/users/log_in" = redir_path = redirected_to(conn, 302)
conn = get(recycle(conn), redir_path)
assert html_response(conn, 200) =~ assert html_response(conn, 200) =~
"You were logged out. Please login again to continue using our application." "You were logged out. Please login again to continue using our application."
end end

View file

@ -6,8 +6,7 @@ defmodule Bones73kWeb.UserLive.ResetPasswordTest do
alias Bones73k.Repo alias Bones73k.Repo
alias Bones73k.Accounts alias Bones73k.Accounts
alias Bones73k.Accounts.User alias Bones73k.Accounts.{User, UserToken}
alias Bones73k.Accounts.UserToken
setup %{conn: conn} do setup %{conn: conn} do
user = user_fixture() user = user_fixture()
@ -46,7 +45,7 @@ defmodule Bones73kWeb.UserLive.ResetPasswordTest do
# Confirm redirected # Confirm redirected
flash = assert_redirected(view, Routes.user_session_path(conn, :new)) flash = assert_redirected(view, Routes.user_session_path(conn, :new))
assert flash["success"] == "Password reset successfully." assert flash["info"] == "Password reset successfully."
# Confirm password was updated # Confirm password was updated
assert Accounts.get_user_by_email_and_password(user.email, new_pw) assert Accounts.get_user_by_email_and_password(user.email, new_pw)

View file

@ -54,9 +54,6 @@ defmodule Bones73kWeb.UserDashboardLiveTest do
assert "/" = redir_path = redirected_to(conn, 302) assert "/" = redir_path = redirected_to(conn, 302)
conn = get(recycle(conn), redir_path) conn = get(recycle(conn), redir_path)
assert "/users/log_in" = redir_path = redirected_to(conn, 302)
conn = get(recycle(conn), redir_path)
assert html_response(conn, 200) =~ assert html_response(conn, 200) =~
"You were logged out. Please login again to continue using our application." "You were logged out. Please login again to continue using our application."
end end