add callsign to users
This commit is contained in:
parent
73d67da457
commit
38920e9f3d
9 changed files with 567 additions and 10 deletions
|
|
@ -181,6 +181,46 @@ defmodule Aprsme.Accounts do
|
|||
UserNotifier.deliver_update_email_instructions(user, update_email_url_fun.(encoded_token))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an `%Ecto.Changeset{}` for changing the user callsign.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> change_user_callsign(user)
|
||||
%Ecto.Changeset{data: %User{}}
|
||||
|
||||
"""
|
||||
def change_user_callsign(user, attrs \\ %{}) do
|
||||
User.callsign_changeset(user, attrs, validate_callsign: false)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates the user callsign with password validation.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> update_user_callsign(user, "valid password", %{callsign: "K1ABC"})
|
||||
{:ok, %User{}}
|
||||
|
||||
iex> update_user_callsign(user, "invalid password", %{callsign: "K1ABC"})
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def update_user_callsign(user, password, attrs) do
|
||||
changeset =
|
||||
user
|
||||
|> User.callsign_changeset(attrs)
|
||||
|> User.validate_current_password(password)
|
||||
|
||||
Ecto.Multi.new()
|
||||
|> Ecto.Multi.update(:user, changeset)
|
||||
|> Repo.transaction()
|
||||
|> case do
|
||||
{:ok, %{user: user}} -> {:ok, user}
|
||||
{:error, :user, changeset, _} -> {:error, changeset}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an `%Ecto.Changeset{}` for changing the user password.
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ defmodule Aprsme.Accounts.User do
|
|||
|
||||
schema "users" do
|
||||
field(:email, :string)
|
||||
field(:callsign, :string)
|
||||
field(:password, :string, virtual: true, redact: true)
|
||||
field(:hashed_password, :string, redact: true)
|
||||
field(:confirmed_at, :naive_datetime)
|
||||
|
|
@ -38,8 +39,9 @@ defmodule Aprsme.Accounts.User do
|
|||
"""
|
||||
def registration_changeset(user, attrs, opts \\ []) do
|
||||
user
|
||||
|> cast(attrs, [:email, :password])
|
||||
|> cast(attrs, [:email, :password, :callsign])
|
||||
|> validate_email(opts)
|
||||
|> validate_callsign(opts)
|
||||
|> validate_password(opts)
|
||||
end
|
||||
|
||||
|
|
@ -51,6 +53,17 @@ defmodule Aprsme.Accounts.User do
|
|||
|> maybe_validate_unique_email(opts)
|
||||
end
|
||||
|
||||
defp validate_callsign(changeset, opts) do
|
||||
changeset
|
||||
|> validate_required([:callsign])
|
||||
|> validate_format(:callsign, ~r/^[A-Z]{1,2}[0-9]{1,2}[A-Z]{1,3}$/i,
|
||||
message: "must be a valid amateur radio callsign"
|
||||
)
|
||||
|> update_change(:callsign, &String.upcase/1)
|
||||
|> validate_length(:callsign, min: 3, max: 10)
|
||||
|> maybe_validate_unique_callsign(opts)
|
||||
end
|
||||
|
||||
defp validate_password(changeset, opts) do
|
||||
changeset
|
||||
|> validate_required([:password])
|
||||
|
|
@ -95,6 +108,19 @@ defmodule Aprsme.Accounts.User do
|
|||
|
||||
defp do_validate_unique_email(changeset, false), do: changeset
|
||||
|
||||
defp maybe_validate_unique_callsign(changeset, opts) do
|
||||
validate_callsign? = Keyword.get(opts, :validate_callsign, true)
|
||||
do_validate_unique_callsign(changeset, validate_callsign?)
|
||||
end
|
||||
|
||||
defp do_validate_unique_callsign(changeset, true) do
|
||||
changeset
|
||||
|> unsafe_validate_unique(:callsign, Aprsme.Repo)
|
||||
|> unique_constraint(:callsign)
|
||||
end
|
||||
|
||||
defp do_validate_unique_callsign(changeset, false), do: changeset
|
||||
|
||||
@doc """
|
||||
A user changeset for changing the email.
|
||||
|
||||
|
|
@ -110,6 +136,21 @@ defmodule Aprsme.Accounts.User do
|
|||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
A user changeset for changing the callsign.
|
||||
|
||||
It requires the callsign to change otherwise an error is added.
|
||||
"""
|
||||
def callsign_changeset(user, attrs, opts \\ []) do
|
||||
user
|
||||
|> cast(attrs, [:callsign])
|
||||
|> validate_callsign(opts)
|
||||
|> case do
|
||||
%{changes: %{callsign: _}} = changeset -> changeset
|
||||
%{} = changeset -> add_error(changeset, :callsign, "did not change")
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
A user changeset for changing the password.
|
||||
|
||||
|
|
|
|||
|
|
@ -60,6 +60,28 @@ defmodule AprsmeWeb.UserRegistrationLive do
|
|||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-control w-full">
|
||||
<label class="label">
|
||||
<span class="label-text">Callsign</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name={Phoenix.HTML.Form.input_name(f, :callsign)}
|
||||
value={Phoenix.HTML.Form.input_value(f, :callsign) || ""}
|
||||
class={[
|
||||
"input input-bordered w-full bg-base-100 text-base-content",
|
||||
Keyword.has_key?(@changeset.errors, :callsign) && "input-error"
|
||||
]}
|
||||
placeholder="Enter your amateur radio callsign"
|
||||
required
|
||||
/>
|
||||
<label :if={Keyword.has_key?(@changeset.errors, :callsign)} class="label">
|
||||
<span class="label-text-alt text-error">
|
||||
{translate_error(Keyword.get(@changeset.errors, :callsign))}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-control w-full">
|
||||
<label class="label">
|
||||
<span class="label-text">Password</span>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ defmodule AprsmeWeb.UserSettingsLive do
|
|||
@moduledoc false
|
||||
use AprsmeWeb, :live_view
|
||||
|
||||
import AprsmeWeb.CoreComponents, only: [translate_error: 1, simple_form: 1]
|
||||
|
||||
alias Aprsme.Accounts
|
||||
|
||||
def render(assigns) do
|
||||
|
|
@ -10,10 +12,10 @@ defmodule AprsmeWeb.UserSettingsLive do
|
|||
<div class="hero-content flex-col w-full max-w-4xl">
|
||||
<div class="text-center mb-8">
|
||||
<h1 class="text-4xl font-bold">Account Settings</h1>
|
||||
<p class="text-base-content/70">Update your email address and password</p>
|
||||
<p class="text-base-content/70">Update your email address, callsign, and password</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-8 w-full">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8 w-full">
|
||||
<!-- Change Email Section -->
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card-body">
|
||||
|
|
@ -38,10 +40,18 @@ defmodule AprsmeWeb.UserSettingsLive do
|
|||
type="email"
|
||||
name={Phoenix.HTML.Form.input_name(f, :email)}
|
||||
value={Phoenix.HTML.Form.input_value(f, :email) || ""}
|
||||
class="input input-bordered w-full bg-base-100 text-base-content"
|
||||
class={[
|
||||
"input input-bordered w-full bg-base-100 text-base-content",
|
||||
Keyword.has_key?(@email_changeset.errors, :email) && "input-error"
|
||||
]}
|
||||
placeholder="Enter your email"
|
||||
required
|
||||
/>
|
||||
<label :if={Keyword.has_key?(@email_changeset.errors, :email)} class="label">
|
||||
<span class="label-text-alt text-error">
|
||||
{translate_error(Keyword.get(@email_changeset.errors, :email))}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-control w-full">
|
||||
|
|
@ -53,10 +63,21 @@ defmodule AprsmeWeb.UserSettingsLive do
|
|||
name="current_password"
|
||||
id="current_password_for_email"
|
||||
value={@email_form_current_password || ""}
|
||||
class="input input-bordered w-full bg-base-100 text-base-content"
|
||||
class={[
|
||||
"input input-bordered w-full bg-base-100 text-base-content",
|
||||
Keyword.has_key?(@email_changeset.errors, :current_password) && "input-error"
|
||||
]}
|
||||
placeholder="Enter current password"
|
||||
required
|
||||
/>
|
||||
<label
|
||||
:if={Keyword.has_key?(@email_changeset.errors, :current_password)}
|
||||
class="label"
|
||||
>
|
||||
<span class="label-text-alt text-error">
|
||||
{translate_error(Keyword.get(@email_changeset.errors, :current_password))}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-control mt-6">
|
||||
|
|
@ -68,6 +89,79 @@ defmodule AprsmeWeb.UserSettingsLive do
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Change Callsign Section -->
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-2xl mb-4">Change Callsign</h2>
|
||||
|
||||
<.simple_form
|
||||
:let={f}
|
||||
id="callsign_form"
|
||||
for={@callsign_changeset}
|
||||
phx-submit="update_callsign"
|
||||
phx-change="validate_callsign"
|
||||
>
|
||||
<div :if={@callsign_changeset.action == :insert} class="alert alert-error mb-4">
|
||||
<span>Oops, something went wrong! Please check the errors below.</span>
|
||||
</div>
|
||||
|
||||
<div class="form-control w-full">
|
||||
<label class="label">
|
||||
<span class="label-text">Callsign</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name={Phoenix.HTML.Form.input_name(f, :callsign)}
|
||||
value={Phoenix.HTML.Form.input_value(f, :callsign) || ""}
|
||||
class={[
|
||||
"input input-bordered w-full bg-base-100 text-base-content",
|
||||
Keyword.has_key?(@callsign_changeset.errors, :callsign) && "input-error"
|
||||
]}
|
||||
placeholder="Enter your callsign"
|
||||
required
|
||||
/>
|
||||
<label :if={Keyword.has_key?(@callsign_changeset.errors, :callsign)} class="label">
|
||||
<span class="label-text-alt text-error">
|
||||
{translate_error(Keyword.get(@callsign_changeset.errors, :callsign))}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-control w-full">
|
||||
<label class="label">
|
||||
<span class="label-text">Current password</span>
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
name="current_password"
|
||||
id="current_password_for_callsign"
|
||||
value={@callsign_form_current_password || ""}
|
||||
class={[
|
||||
"input input-bordered w-full bg-base-100 text-base-content",
|
||||
Keyword.has_key?(@callsign_changeset.errors, :current_password) && "input-error"
|
||||
]}
|
||||
placeholder="Enter current password"
|
||||
required
|
||||
/>
|
||||
<label
|
||||
:if={Keyword.has_key?(@callsign_changeset.errors, :current_password)}
|
||||
class="label"
|
||||
>
|
||||
<span class="label-text-alt text-error">
|
||||
{translate_error(Keyword.get(@callsign_changeset.errors, :current_password))}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-control mt-6">
|
||||
<button type="submit" class="btn btn-primary w-full" phx-disable-with="Changing...">
|
||||
Change Callsign
|
||||
</button>
|
||||
</div>
|
||||
</.simple_form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Change Password Section -->
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card-body">
|
||||
|
|
@ -101,10 +195,18 @@ defmodule AprsmeWeb.UserSettingsLive do
|
|||
type="password"
|
||||
name={Phoenix.HTML.Form.input_name(f, :password)}
|
||||
value={Phoenix.HTML.Form.input_value(f, :password) || ""}
|
||||
class="input input-bordered w-full bg-base-100 text-base-content"
|
||||
class={[
|
||||
"input input-bordered w-full bg-base-100 text-base-content",
|
||||
Keyword.has_key?(@password_changeset.errors, :password) && "input-error"
|
||||
]}
|
||||
placeholder="Enter new password"
|
||||
required
|
||||
/>
|
||||
<label :if={Keyword.has_key?(@password_changeset.errors, :password)} class="label">
|
||||
<span class="label-text-alt text-error">
|
||||
{translate_error(Keyword.get(@password_changeset.errors, :password))}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-control w-full">
|
||||
|
|
@ -115,10 +217,24 @@ defmodule AprsmeWeb.UserSettingsLive do
|
|||
type="password"
|
||||
name={Phoenix.HTML.Form.input_name(f, :password_confirmation)}
|
||||
value={Phoenix.HTML.Form.input_value(f, :password_confirmation) || ""}
|
||||
class="input input-bordered w-full bg-base-100 text-base-content"
|
||||
class={[
|
||||
"input input-bordered w-full bg-base-100 text-base-content",
|
||||
Keyword.has_key?(@password_changeset.errors, :password_confirmation) &&
|
||||
"input-error"
|
||||
]}
|
||||
placeholder="Confirm new password"
|
||||
required
|
||||
/>
|
||||
<label
|
||||
:if={Keyword.has_key?(@password_changeset.errors, :password_confirmation)}
|
||||
class="label"
|
||||
>
|
||||
<span class="label-text-alt text-error">
|
||||
{translate_error(
|
||||
Keyword.get(@password_changeset.errors, :password_confirmation)
|
||||
)}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-control w-full">
|
||||
|
|
@ -130,10 +246,21 @@ defmodule AprsmeWeb.UserSettingsLive do
|
|||
name="current_password"
|
||||
id="current_password_for_password"
|
||||
value={@current_password || ""}
|
||||
class="input input-bordered w-full bg-base-100 text-base-content"
|
||||
class={[
|
||||
"input input-bordered w-full bg-base-100 text-base-content",
|
||||
Keyword.has_key?(@password_changeset.errors, :current_password) && "input-error"
|
||||
]}
|
||||
placeholder="Enter current password"
|
||||
required
|
||||
/>
|
||||
<label
|
||||
:if={Keyword.has_key?(@password_changeset.errors, :current_password)}
|
||||
class="label"
|
||||
>
|
||||
<span class="label-text-alt text-error">
|
||||
{translate_error(Keyword.get(@password_changeset.errors, :current_password))}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-control mt-6">
|
||||
|
|
@ -170,8 +297,11 @@ defmodule AprsmeWeb.UserSettingsLive do
|
|||
socket
|
||||
|> assign(:current_password, nil)
|
||||
|> assign(:email_form_current_password, nil)
|
||||
|> assign(:callsign_form_current_password, nil)
|
||||
|> assign(:current_email, user.email)
|
||||
|> assign(:current_callsign, user.callsign)
|
||||
|> assign(:email_changeset, Accounts.change_user_email(user))
|
||||
|> assign(:callsign_changeset, Accounts.change_user_callsign(user))
|
||||
|> assign(:password_changeset, Accounts.change_user_password(user))
|
||||
|> assign(:trigger_submit, false)
|
||||
|
||||
|
|
@ -211,6 +341,33 @@ defmodule AprsmeWeb.UserSettingsLive do
|
|||
end
|
||||
end
|
||||
|
||||
def handle_event("validate_callsign", params, socket) do
|
||||
%{"current_password" => password, "user" => user_params} = params
|
||||
callsign_changeset = Accounts.change_user_callsign(socket.assigns.current_user, user_params)
|
||||
|
||||
socket =
|
||||
assign(socket,
|
||||
callsign_changeset: Map.put(callsign_changeset, :action, :validate),
|
||||
callsign_form_current_password: password
|
||||
)
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def handle_event("update_callsign", params, socket) do
|
||||
%{"current_password" => password, "user" => user_params} = params
|
||||
user = socket.assigns.current_user
|
||||
|
||||
case Accounts.update_user_callsign(user, password, user_params) do
|
||||
{:ok, _user} ->
|
||||
info = "Callsign updated successfully."
|
||||
{:noreply, socket |> put_flash(:info, info) |> push_navigate(to: ~p"/users/settings")}
|
||||
|
||||
{:error, changeset} ->
|
||||
{:noreply, assign(socket, :callsign_changeset, Map.put(changeset, :action, :insert))}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_event("validate_password", params, socket) do
|
||||
%{"current_password" => password, "user" => user_params} = params
|
||||
password_changeset = Accounts.change_user_password(socket.assigns.current_user, user_params)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
defmodule Aprsme.Repo.Migrations.AddCallsignToUsers do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
alter table(:users) do
|
||||
add :callsign, :string, null: false
|
||||
end
|
||||
|
||||
create unique_index(:users, [:callsign])
|
||||
end
|
||||
end
|
||||
96
test/aprsme/accounts/user_test.exs
Normal file
96
test/aprsme/accounts/user_test.exs
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
defmodule Aprsme.Accounts.UserTest do
|
||||
use Aprsme.DataCase, async: true
|
||||
|
||||
alias Aprsme.Accounts.User
|
||||
|
||||
describe "registration_changeset/2" do
|
||||
test "requires callsign" do
|
||||
changeset = User.registration_changeset(%User{}, %{})
|
||||
errors = errors_on(changeset)
|
||||
assert Map.has_key?(errors, :callsign)
|
||||
assert "can't be blank" in errors.callsign
|
||||
end
|
||||
|
||||
test "validates callsign format" do
|
||||
invalid_callsigns = ["123", "TOOLONGCALLSIGN", "NO SPACES", "nodigit"]
|
||||
|
||||
for callsign <- invalid_callsigns do
|
||||
changeset =
|
||||
User.registration_changeset(%User{}, %{
|
||||
email: "test@example.com",
|
||||
password: "valid_password123",
|
||||
callsign: callsign
|
||||
})
|
||||
|
||||
errors = errors_on(changeset)
|
||||
assert Map.has_key?(errors, :callsign), "Expected callsign error for: #{callsign}"
|
||||
assert "must be a valid amateur radio callsign" in errors.callsign
|
||||
end
|
||||
end
|
||||
|
||||
test "validates empty callsign" do
|
||||
changeset =
|
||||
User.registration_changeset(%User{}, %{
|
||||
email: "test@example.com",
|
||||
password: "valid_password123",
|
||||
callsign: ""
|
||||
})
|
||||
|
||||
errors = errors_on(changeset)
|
||||
assert Map.has_key?(errors, :callsign)
|
||||
assert "can't be blank" in errors.callsign
|
||||
end
|
||||
|
||||
test "accepts valid callsigns" do
|
||||
valid_callsigns = ["K1ABC", "W2XYZ", "AA3BB", "KG4AAA", "N7PQR", "VE3ABC"]
|
||||
|
||||
for callsign <- valid_callsigns do
|
||||
changeset =
|
||||
User.registration_changeset(%User{}, %{
|
||||
email: "test@example.com",
|
||||
password: "valid_password123",
|
||||
callsign: callsign
|
||||
})
|
||||
|
||||
assert changeset.valid?
|
||||
end
|
||||
end
|
||||
|
||||
test "converts callsign to uppercase" do
|
||||
changeset =
|
||||
User.registration_changeset(%User{}, %{
|
||||
email: "test@example.com",
|
||||
password: "valid_password123",
|
||||
callsign: "k1abc"
|
||||
})
|
||||
|
||||
assert get_change(changeset, :callsign) == "K1ABC"
|
||||
end
|
||||
end
|
||||
|
||||
describe "callsign_changeset/2" do
|
||||
test "requires callsign to change" do
|
||||
user = %User{callsign: "K1ABC"}
|
||||
changeset = User.callsign_changeset(user, %{callsign: "K1ABC"})
|
||||
errors = errors_on(changeset)
|
||||
assert Map.has_key?(errors, :callsign)
|
||||
assert "did not change" in errors.callsign
|
||||
end
|
||||
|
||||
test "validates new callsign format" do
|
||||
user = %User{callsign: "K1ABC"}
|
||||
|
||||
changeset = User.callsign_changeset(user, %{callsign: "INVALID"})
|
||||
errors = errors_on(changeset)
|
||||
assert Map.has_key?(errors, :callsign)
|
||||
assert "must be a valid amateur radio callsign" in errors.callsign
|
||||
end
|
||||
|
||||
test "accepts valid callsign change" do
|
||||
user = %User{callsign: "K1ABC"}
|
||||
changeset = User.callsign_changeset(user, %{callsign: "W2XYZ"})
|
||||
assert changeset.valid?
|
||||
assert get_change(changeset, :callsign) == "W2XYZ"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -28,11 +28,12 @@ defmodule AprsmeWeb.UserRegistrationLiveTest do
|
|||
result =
|
||||
lv
|
||||
|> element("#registration_form")
|
||||
|> render_change(user: %{"email" => "with spaces", "password" => "too short"})
|
||||
|> render_change(user: %{"email" => "with spaces", "password" => "too short", "callsign" => "INVALID"})
|
||||
|
||||
assert result =~ "Register"
|
||||
assert result =~ "must have the @ sign and no spaces"
|
||||
assert result =~ "should be at least 12 character"
|
||||
assert result =~ "must be a valid amateur radio callsign"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
183
test/aprsme_web/live/user_settings_live_test.exs
Normal file
183
test/aprsme_web/live/user_settings_live_test.exs
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
defmodule AprsmeWeb.UserSettingsLiveTest do
|
||||
use AprsmeWeb.ConnCase
|
||||
|
||||
import Aprsme.AccountsFixtures
|
||||
import Phoenix.LiveViewTest
|
||||
|
||||
alias Aprsme.Accounts
|
||||
|
||||
describe "Settings page" do
|
||||
setup %{conn: conn} do
|
||||
user = user_fixture()
|
||||
conn = log_in_user(conn, user)
|
||||
{:ok, conn: conn, user: user}
|
||||
end
|
||||
|
||||
test "renders settings page", %{conn: conn} do
|
||||
{:ok, _lv, html} = live(conn, ~p"/users/settings")
|
||||
|
||||
assert html =~ "Change Email"
|
||||
assert html =~ "Change Callsign"
|
||||
assert html =~ "Change Password"
|
||||
end
|
||||
|
||||
test "redirects if user is not logged in" do
|
||||
conn = build_conn()
|
||||
|
||||
{:error, redirect} = live(conn, ~p"/users/settings")
|
||||
|
||||
assert {:redirect, %{to: path, flash: flash}} = redirect
|
||||
assert path == ~p"/users/log_in"
|
||||
assert %{"error" => "You must log in to access this page."} = flash
|
||||
end
|
||||
end
|
||||
|
||||
describe "update callsign form" do
|
||||
setup %{conn: conn} do
|
||||
user = user_fixture()
|
||||
conn = log_in_user(conn, user)
|
||||
{:ok, conn: conn, user: user}
|
||||
end
|
||||
|
||||
test "updates the user callsign", %{conn: conn, user: user} do
|
||||
{:ok, lv, _html} = live(conn, ~p"/users/settings")
|
||||
|
||||
lv
|
||||
|> form("#callsign_form", %{
|
||||
"current_password" => valid_user_password(),
|
||||
"user" => %{"callsign" => "W9NEW"}
|
||||
})
|
||||
|> render_submit()
|
||||
|
||||
# The form should redirect after success
|
||||
flash = assert_redirect(lv, ~p"/users/settings")
|
||||
assert flash["info"] == "Callsign updated successfully."
|
||||
|
||||
# Verify the callsign was updated
|
||||
updated_user = Accounts.get_user!(user.id)
|
||||
assert updated_user.callsign == "W9NEW"
|
||||
end
|
||||
|
||||
test "renders errors with invalid callsign", %{conn: conn} do
|
||||
{:ok, lv, _html} = live(conn, ~p"/users/settings")
|
||||
|
||||
result =
|
||||
lv
|
||||
|> form("#callsign_form", %{
|
||||
"current_password" => valid_user_password(),
|
||||
"user" => %{"callsign" => "INVALID"}
|
||||
})
|
||||
|> render_submit()
|
||||
|
||||
assert result =~ "must be a valid amateur radio callsign"
|
||||
end
|
||||
|
||||
test "renders errors with invalid password", %{conn: conn, user: user} do
|
||||
{:ok, lv, _html} = live(conn, ~p"/users/settings")
|
||||
|
||||
result =
|
||||
lv
|
||||
|> form("#callsign_form", %{
|
||||
"current_password" => "invalid",
|
||||
"user" => %{"callsign" => "W9NEW"}
|
||||
})
|
||||
|> render_submit()
|
||||
|
||||
assert result =~ "is not valid"
|
||||
|
||||
# Verify the callsign was not updated
|
||||
updated_user = Accounts.get_user!(user.id)
|
||||
assert updated_user.callsign == user.callsign
|
||||
end
|
||||
end
|
||||
|
||||
describe "update email form" do
|
||||
setup %{conn: conn} do
|
||||
user = user_fixture()
|
||||
conn = log_in_user(conn, user)
|
||||
{:ok, conn: conn, user: user}
|
||||
end
|
||||
|
||||
test "updates the user email", %{conn: conn, user: user} do
|
||||
{:ok, lv, _html} = live(conn, ~p"/users/settings")
|
||||
|
||||
result =
|
||||
lv
|
||||
|> form("#email_form", %{
|
||||
"current_password" => valid_user_password(),
|
||||
"user" => %{"email" => unique_user_email()}
|
||||
})
|
||||
|> render_submit()
|
||||
|
||||
assert result =~ "A link to confirm your email"
|
||||
assert Accounts.get_user_by_email(user.email)
|
||||
end
|
||||
|
||||
test "renders errors with invalid data (phx-change)", %{conn: conn} do
|
||||
{:ok, lv, _html} = live(conn, ~p"/users/settings")
|
||||
|
||||
result =
|
||||
lv
|
||||
|> element("#email_form")
|
||||
|> render_change(%{
|
||||
"current_password" => valid_user_password(),
|
||||
"user" => %{"email" => "with spaces"}
|
||||
})
|
||||
|
||||
assert result =~ "must have the @ sign and no spaces"
|
||||
end
|
||||
end
|
||||
|
||||
describe "update password form" do
|
||||
setup %{conn: conn} do
|
||||
user = user_fixture()
|
||||
conn = log_in_user(conn, user)
|
||||
{:ok, conn: conn, user: user}
|
||||
end
|
||||
|
||||
test "updates the user password", %{conn: conn, user: user} do
|
||||
{:ok, lv, _html} = live(conn, ~p"/users/settings")
|
||||
|
||||
form =
|
||||
form(lv, "#password_form", %{
|
||||
"current_password" => valid_user_password(),
|
||||
"user" => %{
|
||||
"email" => user.email,
|
||||
"password" => "new valid password",
|
||||
"password_confirmation" => "new valid password"
|
||||
}
|
||||
})
|
||||
|
||||
render_submit(form)
|
||||
|
||||
new_password_conn = follow_trigger_action(form, conn)
|
||||
|
||||
assert redirected_to(new_password_conn) == ~p"/users/settings"
|
||||
|
||||
assert get_session(new_password_conn, :user_token) != get_session(conn, :user_token)
|
||||
|
||||
assert Phoenix.Flash.get(new_password_conn.assigns.flash, :info) =~
|
||||
"Password updated successfully"
|
||||
|
||||
assert Accounts.get_user_by_email_and_password(user.email, "new valid password")
|
||||
end
|
||||
|
||||
test "renders errors with invalid data (phx-change)", %{conn: conn} do
|
||||
{:ok, lv, _html} = live(conn, ~p"/users/settings")
|
||||
|
||||
result =
|
||||
lv
|
||||
|> element("#password_form")
|
||||
|> render_change(%{
|
||||
"current_password" => valid_user_password(),
|
||||
"user" => %{
|
||||
"password" => "short",
|
||||
"password_confirmation" => "does not match"
|
||||
}
|
||||
})
|
||||
|
||||
assert result =~ "should be at least 12 character"
|
||||
assert result =~ "does not match password"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -7,10 +7,16 @@ defmodule Aprsme.AccountsFixtures do
|
|||
def unique_user_email, do: "user#{System.unique_integer()}@example.com"
|
||||
def valid_user_password, do: "hello world!"
|
||||
|
||||
def unique_user_callsign do
|
||||
num = rem(System.unique_integer([:positive]), 99) + 1
|
||||
"K#{num}ABC"
|
||||
end
|
||||
|
||||
def valid_user_attributes(attrs \\ %{}) do
|
||||
Enum.into(attrs, %{
|
||||
email: unique_user_email(),
|
||||
password: valid_user_password()
|
||||
password: valid_user_password(),
|
||||
callsign: unique_user_callsign()
|
||||
})
|
||||
end
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue