more coverage

This commit is contained in:
Jamil Bou Kheir
2020-06-09 08:57:49 -07:00
parent 223a05eaf1
commit be27209523
14 changed files with 139 additions and 86 deletions

View File

@@ -0,0 +1,5 @@
{
"skip_files": [
"test"
]
}

View File

@@ -8,100 +8,32 @@ defmodule FgHttp.Users do
alias FgHttp.Users.User
@doc """
Returns the list of users.
## Examples
iex> list_users()
[%User{}, ...]
"""
def list_users do
Repo.all(User)
end
@doc """
Gets a single user.
Raises `Ecto.NoResultsError` if the User does not exist.
## Examples
iex> get_user!(123)
%User{}
iex> get_user!(456)
** (Ecto.NoResultsError)
"""
def get_user!(email: email) do
Repo.get_by!(User, email: email)
end
def get_user!(id), do: Repo.get!(User, id)
@doc """
Creates a user.
## Examples
iex> create_user(%{field: value})
{:ok, %User{}}
iex> create_user(%{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def create_user(attrs \\ %{}) do
%User{}
|> User.create_changeset(attrs)
|> Repo.insert()
end
@doc """
Updates a user.
## Examples
iex> update_user(user, %{field: new_value})
{:ok, %User{}}
iex> update_user(user, %{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def update_user(%User{} = user, attrs) do
user
|> User.update_changeset(attrs)
|> Repo.update()
end
@doc """
Deletes a user.
## Examples
iex> delete_user(user)
{:ok, %User{}}
iex> delete_user(user)
{:error, %Ecto.Changeset{}}
"""
def delete_user(%User{} = user) do
Repo.delete(user)
end
@doc """
Returns an `%Ecto.Changeset{}` for tracking user changes.
## Examples
iex> change_user(user)
%Ecto.Changeset{source: %User{}}
"""
def change_user(%User{} = user) do
User.changeset(user, %{})
end

View File

@@ -7,7 +7,7 @@ defmodule FgHttp.Users.User do
import Ecto.Changeset
import FgHttp.Users.PasswordHelpers
alias FgHttp.Devices.Device
alias FgHttp.{Devices.Device, Util.FgMap}
schema "users" do
field :email, :string
@@ -36,6 +36,17 @@ defmodule FgHttp.Users.User do
end
# Password updated with user logged in
def update_changeset(
user,
%{
"password" => nil,
"password_confirmation" => nil,
"current_password" => nil
} = attrs
) do
update_changeset(user, FgMap.compact(attrs))
end
def update_changeset(
user,
%{
@@ -47,7 +58,7 @@ defmodule FgHttp.Users.User do
user
|> cast(attrs, [:email, :password, :password_confirmation, :current_password])
|> validate_required([:password, :password_confirmation, :current_password])
|> verify_current_password(attrs[:current_password])
|> verify_current_password(user)
|> validate_password_equality()
|> put_password_hash()
|> validate_required([:password_hash])
@@ -76,16 +87,24 @@ defmodule FgHttp.Users.User do
|> validate_required([:email])
end
def changeset(%__MODULE__{} = _user, _attrs \\ %{}) do
change(%__MODULE__{})
def changeset(user, attrs) do
user
|> cast(attrs, [:email, :confirmed_at, :last_signed_in_at])
end
def authenticate_user(user, password_candidate) do
Argon2.check_pass(user, password_candidate)
end
defp verify_current_password(user, current_password) do
{:ok, user} = authenticate_user(user, current_password)
user
defp verify_current_password(changeset, user) do
case authenticate_user(user, changeset.changes.current_password) do
{:ok, _user} ->
changeset
|> delete_change(:current_password)
{:error, error_msg} ->
changeset
|> add_error(:current_password, "is invalid: #{error_msg}")
end
end
end

View File

@@ -0,0 +1,12 @@
defmodule FgHttp.Util.FgMap do
@moduledoc """
Utilities for working with Maps
"""
@doc """
Removes key, value pairs from a Map if the value is nil
"""
def compact(%{} = map) do
for {k, v} <- map, v != nil, into: %{}, do: {k, v}
end
end

View File

@@ -7,6 +7,7 @@ defmodule FgHttpWeb.UserController do
alias FgHttp.{Sessions, Users, Users.User}
plug FgHttpWeb.Plugs.SessionLoader when action in [:show, :edit, :update, :delete]
plug :scrub_params, "user" when action in [:update]
# GET /users/new
def new(conn, _params) do

View File

@@ -16,6 +16,7 @@
</nav>
<nav class="f6 fw6 ttu tracked fr">
<%= if assigns[:session] do %>
<%= link("Your Account", to: Routes.user_path(@conn, :show), class: "link dim white dib mr3") %>
<%= link("Devices", to: Routes.device_path(@conn, :index), class: "link dim white dib mr3") %>
<%= link("Sign out", to: Routes.session_path(@conn, :delete), method: :delete, class: "link dim white dib mr3") %>
<% else %>

View File

@@ -9,10 +9,12 @@
<div class="mt3">
<%= label(:session_user, :email, class: "db fw6 lh-copy f6") %>
<%= text_input(f, :email, class: "pa2 input-reset ba bg-transparent hover-bg-black hover-white w-100") %>
<%= error_tag f, :email %>
</div>
<div class="mv3">
<%= label(:session_user, :password, class: "db fw6 lh-copy f6") %>
<%= password_input(f, :password, class: "pa2 input-reset ba bg-transparent hover-bg-black hover-white w-100") %>
<%= error_tag f, :password %>
</div>
<label class="pa0 ma0 lh-copy f6 pointer"><input type="checkbox"> Remember me</label>
</fieldset>

View File

@@ -1 +1,34 @@
<h3>Edit Account</h3>
<%= form_for @changeset, Routes.user_path(@conn, :update), fn f -> %>
<%= if @changeset.action do %>
<div>
<p>Oops, something went wrong! Please check the errors below.</p>
</div>
<% end %>
<fieldset id="edit_account" class="ba b--transparent ph0 mh0">
<legend class="f4 fw6 ph0 mh0">Edit Account</legend>
<div class="mt3">
<%= label(f, :email, class: "db fw6 lh-copy f6") %>
<%= text_input(f, :email, class: "pa2 input-reset ba bg-transparent hover-bg-black hover-white w-100") %>
<%= error_tag f, :email %>
</div>
<div class="mv3">
<%= label(f, :password, class: "db fw6 lh-copy f6") %>
<%= password_input(f, :password, class: "pa2 input-reset ba bg-transparent hover-bg-black hover-white w-100") %>
<%= error_tag f, :password %>
</div>
<div class="mv3">
<%= label(f, :password_confirmation, class: "db fw6 lh-copy f6") %>
<%= password_input(f, :password_confirmation, class: "pa2 input-reset ba bg-transparent hover-bg-black hover-white w-100") %>
<%= error_tag f, :password_confirmation %>
</div>
<div class="mv3">
<%= label(f, :current_password, class: "db fw6 lh-copy f6") %>
<%= password_input(f, :current_password, class: "pa2 input-reset ba bg-transparent hover-bg-black hover-white w-100") %>
<%= error_tag f, :current_password %>
</div>
</fieldset>
<div>
<%= submit "Save", class: "b ph3 pv2 input-reset ba b--black bg-transparent grow pointer f6 dib" %>
</div>
<% end %>

View File

@@ -1,8 +1,10 @@
<h3>Sign Up</h3>
<%= form_for @changeset, Routes.user_path(@conn, :create), fn f -> %>
<%= if f.errors do %>
<%= form_for @changeset, Routes.user_path(@conn, :new), fn f -> %>
<%= if @changeset.action do %>
<div>
<p>Oops, something went wrong! Please check the errors below.</p>
</div>
<% end %>
<label>

View File

@@ -7,3 +7,9 @@
<dt>Last signed in at</dt>
<dd><%= @session.last_signed_in_at %></dd>
</dl>
<%= link("Edit", to: Routes.user_path(@conn, :edit)) %>
|
<%= link("Delete your account", to: Routes.user_path(@conn, :delete)) %>

View File

@@ -0,0 +1,13 @@
defmodule FgHttp.Util.FgMapTest do
use ExUnit.Case, async: true
alias FgHttp.Util.FgMap
describe "compact" do
test "it compacts the map" do
data = %{foo: nil, bar: "hello"}
assert FgMap.compact(data) == %{bar: "hello"}
end
end
end

View File

@@ -6,7 +6,8 @@ defmodule FgHttpWeb.RuleControllerTest do
@valid_create_attrs %{
destination: "1.1.1.1",
port: "53",
protocol: "udp"
protocol: "udp",
action: "accept"
}
@invalid_create_attrs %{
destination: "problem"

View File

@@ -14,9 +14,21 @@ defmodule FgHttpWeb.UserControllerTest do
password_confirmation: "wrong_password"
}
@valid_update_attrs %{
"email" => "new-email",
"password" => "new_password",
"password_confirmation" => "new_password"
email: "new-email",
password: "new_password",
password_confirmation: "new_password"
}
@valid_update_password_attrs %{
email: "fixture",
password: "new_password",
password_confirmation: "new_password",
current_password: "test"
}
@invalid_update_password_attrs %{
email: "fixture",
password: "new_password",
password_confirmation: "new_password",
current_password: "wrong current password"
}
@invalid_update_attrs %{
email: "new-email",
@@ -63,6 +75,20 @@ defmodule FgHttpWeb.UserControllerTest do
end
end
describe "update password" do
test "updates password when params are valid", %{authed_conn: conn} do
test_conn = put(conn, Routes.user_path(conn, :update), user: @valid_update_password_attrs)
assert redirected_to(test_conn) == Routes.user_path(test_conn, :show)
end
test "renders errors when params are invalid", %{authed_conn: conn} do
test_conn = put(conn, Routes.user_path(conn, :update), user: @invalid_update_password_attrs)
assert html_response(test_conn, 200) =~ "is invalid: invalid password"
end
end
describe "update" do
test "updates user when params are valid", %{authed_conn: conn} do
test_conn = put(conn, Routes.user_path(conn, :update), user: @valid_update_attrs)
@@ -73,7 +99,7 @@ defmodule FgHttpWeb.UserControllerTest do
test "renders errors when params are invalid", %{authed_conn: conn} do
test_conn = put(conn, Routes.user_path(conn, :update), user: @invalid_update_attrs)
assert html_response(test_conn, 200) =~ "Edit Account"
assert html_response(test_conn, 200) =~ "does not match password confirmation"
end
end

View File

@@ -32,10 +32,10 @@
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.2.2", "38d94c30df5e2ef11000697a4fbe2b38d0fbf79239d492ff1be87bbc33bc3a84", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "a3dec3d28ddb5476c96a7c8a38ea8437923408bc88da43e5c45d97037b396280"},
"phoenix_live_view": {:hex, :phoenix_live_view, "0.12.1", "42f591c781edbf9fab921319076b7ac635d43aa23e6748d2644563326236d7e4", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.4.16 or ~> 1.5.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14", [hex: :phoenix_html, repo: "hexpm", optional: false]}], "hexpm", "585321e98df1cd5943e370b9784e950a37ca073744eb534660c9048967c52ab6"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"},
"plug": {:hex, :plug, "1.10.1", "c56a6d9da7042d581159bcbaef873ba9d87f15dce85420b0d287bca19f40f9bd", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "b5cd52259817eb8a31f2454912ba1cff4990bca7811918878091cb2ab9e52cb8"},
"plug": {:hex, :plug, "1.10.2", "0079345cfdf9e17da3858b83eb46bc54beb91554c587b96438f55c1477af5a86", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7898d0eb4767efb3b925fd7f9d1870d15e66e9c33b89c58d8d2ad89aa75ab3c1"},
"plug_cowboy": {:hex, :plug_cowboy, "2.2.2", "7a09aa5d10e79b92d332a288f21cc49406b1b994cbda0fde76160e7f4cc890ea", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e82364b29311dbad3753d588febd7e5ef05062cd6697d8c231e0e007adab3727"},
"plug_crypto": {:hex, :plug_crypto, "1.1.2", "bdd187572cc26dbd95b87136290425f2b580a116d3fb1f564216918c9730d227", [:mix], [], "hexpm", "6b8b608f895b6ffcfad49c37c7883e8df98ae19c6a28113b02aa1e9c5b22d6b5"},
"postgrex": {:hex, :postgrex, "0.15.4", "5d691c25fc79070705a2ff0e35ce0822b86a0ee3c6fdb7a4fb354623955e1aed", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "306515b9d975fcb2478dc337a1d27dc3bf8af7cd71017c333fe9db3a3d211b0a"},
"postgrex": {:hex, :postgrex, "0.15.5", "aec40306a622d459b01bff890fa42f1430dac61593b122754144ad9033a2152f", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "ed90c81e1525f65a2ba2279dbcebf030d6d13328daa2f8088b9661eb9143af7f"},
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
"telemetry": {:hex, :telemetry, "0.4.1", "ae2718484892448a24470e6aa341bc847c3277bfb8d4e9289f7474d752c09c7f", [:rebar3], [], "hexpm", "4738382e36a0a9a2b6e25d67c960e40e1a2c95560b9f936d8e29de8cd858480f"},