improve invites, record usage

This commit is contained in:
2023-02-04 17:22:06 -05:00
parent eb75937587
commit cd7220cea3
37 changed files with 902 additions and 614 deletions

View File

@ -0,0 +1,176 @@
defmodule Memex.InvitesTest do
@moduledoc """
This module tests the Memex.Accounts.Invites context
"""
use Memex.DataCase
alias Ecto.Changeset
alias Memex.Accounts
alias Memex.Accounts.{Invite, Invites}
@moduletag :invites_test
@valid_attrs %{
"name" => "some name"
}
@invalid_attrs %{
"name" => nil,
"token" => nil
}
describe "invites" do
setup do
current_user = admin_fixture()
{:ok, invite} = Invites.create_invite(current_user, @valid_attrs)
[invite: invite, current_user: current_user]
end
test "list_invites/0 returns all invites", %{invite: invite, current_user: current_user} do
assert Invites.list_invites(current_user) == [invite]
end
test "get_invite!/1 returns the invite with given id",
%{invite: invite, current_user: current_user} do
assert Invites.get_invite!(invite.id, current_user) == invite
end
test "valid_invite_token? returns for valid and invalid invite tokens",
%{invite: %{token: token}} do
refute Invites.valid_invite_token?(nil)
refute Invites.valid_invite_token?("")
assert Invites.valid_invite_token?(token)
end
test "valid_invite_token? does not return true for a disabled invite by token",
%{invite: %{token: token} = invite, current_user: current_user} do
assert Invites.valid_invite_token?(token)
{:ok, _invite} = Invites.update_invite(invite, %{uses_left: 1}, current_user)
{:ok, _invite} = Invites.use_invite(token)
refute Invites.valid_invite_token?(token)
end
test "get_use_count/2 returns the correct invite usage",
%{invite: %{token: token} = invite, current_user: current_user} do
assert 0 == Invites.get_use_count(invite, current_user)
assert {:ok, _user} =
Accounts.register_user(
%{"email" => unique_user_email(), "password" => valid_user_password()},
token
)
assert 1 == Invites.get_use_count(invite, current_user)
assert {:ok, _user} =
Accounts.register_user(
%{"email" => unique_user_email(), "password" => valid_user_password()},
token
)
assert 2 == Invites.get_use_count(invite, current_user)
end
test "use_invite/1 successfully uses an unlimited invite",
%{invite: %{token: token} = invite, current_user: current_user} do
{:ok, invite} = Invites.update_invite(invite, %{uses_left: nil}, current_user)
assert {:ok, ^invite} = Invites.use_invite(token)
assert {:ok, ^invite} = Invites.use_invite(token)
assert {:ok, ^invite} = Invites.use_invite(token)
end
test "use_invite/1 successfully decrements an invite",
%{invite: %{token: token} = invite, current_user: current_user} do
{:ok, _invite} = Invites.update_invite(invite, %{uses_left: 10}, current_user)
assert {:ok, %{uses_left: 9}} = Invites.use_invite(token)
assert {:ok, %{uses_left: 8}} = Invites.use_invite(token)
assert {:ok, %{uses_left: 7}} = Invites.use_invite(token)
end
test "use_invite/1 successfully disactivates an invite",
%{invite: %{token: token} = invite, current_user: current_user} do
{:ok, _invite} = Invites.update_invite(invite, %{uses_left: 1}, current_user)
assert {:ok, %{uses_left: 0, disabled_at: disabled_at}} = Invites.use_invite(token)
assert not is_nil(disabled_at)
end
test "use_invite/1 does not work on disactivated invite",
%{invite: %{token: token} = invite, current_user: current_user} do
{:ok, _invite} = Invites.update_invite(invite, %{uses_left: 1}, current_user)
{:ok, _invite} = Invites.use_invite(token)
assert {:error, :invalid_token} = Invites.use_invite(token)
end
test "create_invite/1 with valid data creates an unlimited invite",
%{current_user: current_user} do
assert {:ok, %Invite{} = invite} =
Invites.create_invite(current_user, %{
"name" => "some name"
})
assert invite.name == "some name"
end
test "create_invite/1 with valid data creates a limited invite",
%{current_user: current_user} do
assert {:ok, %Invite{} = invite} =
Invites.create_invite(current_user, %{
"name" => "some name",
"uses_left" => 10
})
assert invite.name == "some name"
assert invite.uses_left == 10
end
test "create_invite/1 with invalid data returns error changeset",
%{current_user: current_user} do
assert {:error, %Changeset{}} = Invites.create_invite(current_user, @invalid_attrs)
end
test "update_invite/2 can set an invite to be limited",
%{invite: invite, current_user: current_user} do
assert {:ok, %Invite{} = new_invite} =
Invites.update_invite(
invite,
%{"name" => "some updated name", "uses_left" => 5},
current_user
)
assert new_invite.name == "some updated name"
assert new_invite.uses_left == 5
end
test "update_invite/2 can set an invite to be unlimited",
%{invite: invite, current_user: current_user} do
{:ok, invite} = Invites.update_invite(invite, %{"uses_left" => 5}, current_user)
assert {:ok, %Invite{} = new_invite} =
Invites.update_invite(
invite,
%{"name" => "some updated name", "uses_left" => nil},
current_user
)
assert new_invite.name == "some updated name"
assert new_invite.uses_left |> is_nil()
end
test "update_invite/2 with invalid data returns error changeset",
%{invite: invite, current_user: current_user} do
assert {:error, %Changeset{}} = Invites.update_invite(invite, @invalid_attrs, current_user)
assert invite == Invites.get_invite!(invite.id, current_user)
end
test "delete_invite/1 deletes the invite", %{invite: invite, current_user: current_user} do
assert {:ok, %Invite{}} = Invites.delete_invite(invite, current_user)
assert_raise Ecto.NoResultsError, fn -> Invites.get_invite!(invite.id, current_user) end
end
test "delete_invite!/1 deletes the invite", %{invite: invite, current_user: current_user} do
assert %Invite{} = Invites.delete_invite!(invite, current_user)
assert_raise Ecto.NoResultsError, fn -> Invites.get_invite!(invite.id, current_user) end
end
end
end

View File

@ -6,7 +6,7 @@ defmodule Memex.AccountsTest do
use Memex.DataCase
alias Ecto.Changeset
alias Memex.Accounts
alias Memex.Accounts.{User, UserToken}
alias Memex.Accounts.{Invites, User, UserToken}
@moduletag :accounts_test
@ -102,6 +102,17 @@ defmodule Memex.AccountsTest do
assert is_nil(user.confirmed_at)
assert is_nil(user.password)
end
test "records used invite during registration" do
{:ok, %{id: invite_id, token: token}} =
admin_fixture() |> Invites.create_invite(%{"name" => "my invite"})
assert {:ok, %{invite_id: ^invite_id}} =
Accounts.register_user(
%{"email" => unique_user_email(), "password" => valid_user_password()},
token
)
end
end
describe "change_user_registration/2" do

View File

@ -1,76 +0,0 @@
defmodule Memex.InvitesTest do
@moduledoc """
This module tests the Invites context
"""
use Memex.DataCase
alias Ecto.Changeset
alias Memex.{Invites, Invites.Invite}
@moduletag :invites_test
@valid_attrs %{
"name" => "some name",
"token" => "some token"
}
@update_attrs %{
"name" => "some updated name",
"token" => "some updated token"
}
@invalid_attrs %{
"name" => nil,
"token" => nil
}
describe "invites" do
setup do
current_user = admin_fixture()
{:ok, invite} = Invites.create_invite(current_user, @valid_attrs)
[invite: invite, current_user: current_user]
end
test "list_invites/0 returns all invites", %{invite: invite, current_user: current_user} do
assert Invites.list_invites(current_user) == [invite]
end
test "get_invite!/1 returns the invite with given id",
%{invite: invite, current_user: current_user} do
assert Invites.get_invite!(invite.id, current_user) == invite
end
test "create_invite/1 with valid data creates a invite",
%{current_user: current_user} do
assert {:ok, %Invite{} = invite} = Invites.create_invite(current_user, @valid_attrs)
assert invite.name == "some name"
end
test "create_invite/1 with invalid data returns error changeset",
%{current_user: current_user} do
assert {:error, %Changeset{}} = Invites.create_invite(current_user, @invalid_attrs)
end
test "update_invite/2 with valid data updates the invite",
%{invite: invite, current_user: current_user} do
assert {:ok, %Invite{} = new_invite} =
Invites.update_invite(invite, @update_attrs, current_user)
assert new_invite.name == "some updated name"
assert new_invite.token == new_invite.token
end
test "update_invite/2 with invalid data returns error changeset",
%{invite: invite, current_user: current_user} do
assert {:error, %Changeset{}} = Invites.update_invite(invite, @invalid_attrs, current_user)
assert invite == Invites.get_invite!(invite.id, current_user)
end
test "delete_invite/1 deletes the invite", %{invite: invite, current_user: current_user} do
assert {:ok, %Invite{}} = Invites.delete_invite(invite, current_user)
assert_raise Ecto.NoResultsError, fn -> Invites.get_invite!(invite.id, current_user) end
end
test "change_invite/1 returns a invite changeset", %{invite: invite} do
assert %Changeset{} = Invites.change_invite(invite)
end
end
end

View File

@ -8,14 +8,7 @@ defmodule MemexWeb.ExportControllerTest do
@moduletag :export_controller_test
setup %{conn: conn} do
current_user = user_fixture() |> confirm_user()
[
current_user: current_user,
conn: conn |> log_in_user(current_user)
]
end
setup [:register_and_log_in_user]
defp add_data(%{current_user: current_user}) do
note = note_fixture(current_user)

View File

@ -33,7 +33,7 @@ defmodule MemexWeb.UserRegistrationControllerTest do
})
assert get_session(conn, :phoenix_flash) == %{
"info" => dgettext("prompts", "Please check your email to verify your account")
"info" => dgettext("prompts", "please check your email to verify your account")
}
assert redirected_to(conn) =~ "/"

View File

@ -15,7 +15,7 @@ defmodule MemexWeb.UserSettingsControllerTest do
test "renders settings page", %{conn: conn} do
conn = get(conn, Routes.user_settings_path(conn, :edit))
response = html_response(conn, 200)
assert response =~ gettext("Settings")
assert response =~ gettext("settings")
end
test "redirects if user is not logged in" do
@ -27,7 +27,7 @@ defmodule MemexWeb.UserSettingsControllerTest do
describe "PUT /users/settings (change password form)" do
test "updates the user password and resets tokens",
%{conn: conn, user: user} do
%{conn: conn, current_user: current_user} do
new_password_conn =
put(conn, Routes.user_settings_path(conn, :update), %{
"action" => "update_password",
@ -42,9 +42,9 @@ defmodule MemexWeb.UserSettingsControllerTest do
assert get_session(new_password_conn, :user_token) != get_session(conn, :user_token)
assert get_flash(new_password_conn, :info) =~
dgettext("actions", "Password updated successfully")
dgettext("actions", "password updated successfully")
assert Accounts.get_user_by_email_and_password(user.email, "new valid password")
assert Accounts.get_user_by_email_and_password(current_user.email, "new valid password")
end
test "does not update password on invalid data", %{conn: conn} do
@ -70,7 +70,7 @@ defmodule MemexWeb.UserSettingsControllerTest do
describe "PUT /users/settings (change email form)" do
@tag :capture_log
test "updates the user email", %{conn: conn, user: user} do
test "updates the user email", %{conn: conn, current_user: current_user} do
conn =
put(conn, Routes.user_settings_path(conn, :update), %{
"action" => "update_email",
@ -83,10 +83,10 @@ defmodule MemexWeb.UserSettingsControllerTest do
assert get_flash(conn, :info) =~
dgettext(
"prompts",
"A link to confirm your email change has been sent to the new address."
"a link to confirm your email change has been sent to the new address."
)
assert Accounts.get_user_by_email(user.email)
assert Accounts.get_user_by_email(current_user.email)
end
test "does not update email on invalid data", %{conn: conn} do
@ -105,14 +105,14 @@ defmodule MemexWeb.UserSettingsControllerTest do
end
describe "GET /users/settings/confirm_email/:token" do
setup %{user: user} do
setup %{current_user: current_user} do
email = unique_user_email()
token =
extract_user_token(fn url ->
Accounts.deliver_update_email_instructions(
%{user | email: email},
user.email,
%{current_user | email: email},
current_user.email,
url
)
end)
@ -121,28 +121,28 @@ defmodule MemexWeb.UserSettingsControllerTest do
end
test "updates the user email once",
%{conn: conn, user: user, token: token, email: email} do
%{conn: conn, current_user: current_user, token: token, email: email} do
conn = get(conn, Routes.user_settings_path(conn, :confirm_email, token))
assert redirected_to(conn) == Routes.user_settings_path(conn, :edit)
assert get_flash(conn, :info) =~ dgettext("prompts", "Email changed successfully")
refute Accounts.get_user_by_email(user.email)
assert get_flash(conn, :info) =~ dgettext("prompts", "email changed successfully")
refute Accounts.get_user_by_email(current_user.email)
assert Accounts.get_user_by_email(email)
conn = get(conn, Routes.user_settings_path(conn, :confirm_email, token))
assert redirected_to(conn) == Routes.user_settings_path(conn, :edit)
assert get_flash(conn, :error) =~
dgettext("errors", "Email change link is invalid or it has expired")
dgettext("errors", "email change link is invalid or it has expired")
end
test "does not update email with invalid token", %{conn: conn, user: user} do
test "does not update email with invalid token", %{conn: conn, current_user: current_user} do
conn = get(conn, Routes.user_settings_path(conn, :confirm_email, "oops"))
assert redirected_to(conn) == Routes.user_settings_path(conn, :edit)
assert get_flash(conn, :error) =~
dgettext("errors", "Email change link is invalid or it has expired")
dgettext("errors", "email change link is invalid or it has expired")
assert Accounts.get_user_by_email(user.email)
assert Accounts.get_user_by_email(current_user.email)
end
test "redirects if user is not logged in", %{token: token} do

View File

@ -23,8 +23,8 @@ defmodule MemexWeb.ContextLiveTest do
"visibility" => nil
}
defp create_context(%{user: user}) do
[context: context_fixture(user)]
defp create_context(%{current_user: current_user}) do
[context: context_fixture(current_user)]
end
describe "Index" do
@ -148,13 +148,16 @@ defmodule MemexWeb.ContextLiveTest do
describe "show with note" do
setup [:register_and_log_in_user]
setup %{user: user} do
%{slug: note_slug} = note = note_fixture(user)
setup %{current_user: current_user} do
%{slug: note_slug} = note = note_fixture(current_user)
[
note: note,
context:
context_fixture(%{content: "example with backlink to [[#{note_slug}]] note"}, user)
context_fixture(
%{content: "example with backlink to [[#{note_slug}]] note"},
current_user
)
]
end

View File

@ -6,7 +6,7 @@ defmodule MemexWeb.InviteLiveTest do
use MemexWeb.ConnCase
import Phoenix.LiveViewTest
import MemexWeb.Gettext
alias Memex.Invites
alias Memex.Accounts.Invites
@moduletag :invite_live_test
@create_attrs %{"name" => "some name"}
@ -16,9 +16,9 @@ defmodule MemexWeb.InviteLiveTest do
describe "Index" do
setup [:register_and_log_in_user]
setup %{user: user} do
{:ok, invite} = Invites.create_invite(user, @create_attrs)
%{invite: invite, user: user}
setup %{current_user: current_user} do
{:ok, invite} = Invites.create_invite(current_user, @create_attrs)
%{invite: invite, current_user: current_user}
end
test "lists all invites", %{conn: conn, invite: invite} do

View File

@ -24,8 +24,8 @@ defmodule MemexWeb.NoteLiveTest do
"visibility" => nil
}
defp create_note(%{user: user}) do
[note: note_fixture(user)]
defp create_note(%{current_user: current_user}) do
[note: note_fixture(current_user)]
end
describe "Index" do
@ -149,13 +149,13 @@ defmodule MemexWeb.NoteLiveTest do
describe "show with note" do
setup [:register_and_log_in_user]
setup %{user: user} do
%{slug: note_slug} = note = note_fixture(user)
setup %{current_user: current_user} do
%{slug: note_slug} = note = note_fixture(current_user)
[
note: note,
backlinked_note:
note_fixture(%{content: "example with backlink to [[#{note_slug}]] note"}, user)
note_fixture(%{content: "example with backlink to [[#{note_slug}]] note"}, current_user)
]
end

View File

@ -34,8 +34,8 @@ defmodule MemexWeb.PipelineLiveTest do
"title" => nil
}
defp create_pipeline(%{user: user}) do
[pipeline: pipeline_fixture(user)]
defp create_pipeline(%{current_user: current_user}) do
[pipeline: pipeline_fixture(current_user)]
end
describe "Index" do
@ -181,9 +181,9 @@ defmodule MemexWeb.PipelineLiveTest do
describe "show with a step" do
setup [:register_and_log_in_user, :create_pipeline]
setup %{pipeline: pipeline, user: user} do
setup %{pipeline: pipeline, current_user: current_user} do
[
step: step_fixture(0, pipeline, user)
step: step_fixture(0, pipeline, current_user)
]
end
@ -236,11 +236,11 @@ defmodule MemexWeb.PipelineLiveTest do
describe "show with multiple steps" do
setup [:register_and_log_in_user, :create_pipeline]
setup %{pipeline: pipeline, user: user} do
setup %{pipeline: pipeline, current_user: current_user} do
[
first_step: step_fixture(%{title: "first step"}, 0, pipeline, user),
second_step: step_fixture(%{title: "second step"}, 1, pipeline, user),
third_step: step_fixture(%{title: "third step"}, 2, pipeline, user)
first_step: step_fixture(%{title: "first step"}, 0, pipeline, current_user),
second_step: step_fixture(%{title: "second step"}, 1, pipeline, current_user),
third_step: step_fixture(%{title: "third step"}, 2, pipeline, current_user)
]
end

View File

@ -23,10 +23,11 @@ defmodule MemexWeb.ConnCase do
using do
quote do
# Import conveniences for testing with connections
import Plug.Conn
import Phoenix.ConnTest
# credo:disable-for-next-line Credo.Check.Consistency.MultiAliasImportRequireUse
import Memex.Fixtures
import MemexWeb.ConnCase
import Phoenix.ConnTest
import Plug.Conn
alias MemexWeb.Router.Helpers, as: Routes
@ -50,10 +51,10 @@ defmodule MemexWeb.ConnCase do
test context.
"""
@spec register_and_log_in_user(%{conn: Plug.Conn.t()}) ::
%{conn: Plug.Conn.t(), user: User.t()}
%{conn: Plug.Conn.t(), current_user: User.t()}
def register_and_log_in_user(%{conn: conn}) do
user = user_fixture() |> confirm_user()
%{conn: log_in_user(conn, user), user: user}
current_user = user_fixture() |> confirm_user()
%{conn: log_in_user(conn, current_user), current_user: current_user}
end
@spec confirm_user(User.t()) :: User.t()

View File

@ -22,10 +22,8 @@ defmodule Memex.DataCase do
alias Memex.Repo
import Ecto
import Ecto.Changeset
import Ecto.Query
import Memex.DataCase
import Memex.Fixtures
import Ecto.{Changeset, Query}
import Memex.{DataCase, Fixtures}
end
end
@ -45,7 +43,7 @@ defmodule Memex.DataCase do
"""
def errors_on(changeset) do
Ecto.Changeset.traverse_errors(changeset, fn {message, opts} ->
Regex.replace(~r"%{(\w+)}", message, fn _, key ->
Regex.replace(~r"%{(\w+)}", message, fn _capture, key ->
opts |> Keyword.get(String.to_existing_atom(key), key) |> to_string()
end)
end)