Merge branch 'chore-test-coverage' into 'master'

chore: Increase test coverage throughout the framework

See merge request mythic-insight/legendary!119
This commit is contained in:
Robert Prehn 2021-09-06 19:43:16 +00:00
commit 6893fc8d23
35 changed files with 573 additions and 126 deletions

View file

@ -9,10 +9,11 @@ defmodule AppWeb.LiveHelpers do
end end
def require_auth(socket) do def require_auth(socket) do
if socket.assigns.current_user do case socket.assigns do
socket %{current_user: user} when not is_nil(user) ->
else socket
redirect(socket, to: "/") _ ->
redirect(socket, to: "/")
end end
end end

View file

@ -51,7 +51,7 @@ defmodule AppWeb.Router do
forward "/sent_emails", Bamboo.SentEmailViewerPlug forward "/sent_emails", Bamboo.SentEmailViewerPlug
end end
if Mix.env() == :e2e do if Mix.env() in [:e2e, :test] do
forward("/end-to-end", Legendary.CoreWeb.Plug.TestEndToEnd, otp_app: :app) forward("/end-to-end", Legendary.CoreWeb.Plug.TestEndToEnd, otp_app: :app)
end end

View file

@ -8,10 +8,12 @@ defmodule AppWeb.ErrorHelpers do
@doc """ @doc """
Generates tag for inlined form input errors. Generates tag for inlined form input errors.
""" """
def error_tag(form, field) do def error_tag(form, field, opts \\ []) do
{extra_classes, _rest_opts} = Keyword.pop(opts, :class, "")
Enum.map(Keyword.get_values(form.errors, field), fn error -> Enum.map(Keyword.get_values(form.errors, field), fn error ->
content_tag(:span, translate_error(error), content_tag(:span, translate_error(error),
class: "invalid-feedback", class: "invalid-feedback #{extra_classes}",
phx_feedback_for: input_id(form, field) phx_feedback_for: input_id(form, field)
) )
end) end)

View file

@ -0,0 +1,29 @@
defmodule AppWeb.ErrorHelpersTest do
use AppWeb.ConnCase
import Phoenix.HTML, only: [safe_to_string: 1]
import Phoenix.HTML.Form, only: [form_for: 3]
import AppWeb.ErrorHelpers
def form do
:example
|> form_for(
"/example",
as: :test_params,
errors: [error_field: {"is an error", []}]
)
end
describe "error_tag/2" do
test "generates a span with an invalid-feedback class" do
[safe] = error_tag(form(), :error_field)
assert safe_to_string(safe) =~ "invalid-feedback"
end
test "error_tag/3" do
[safe] = error_tag(form(), :error_field, class: "test-class")
assert safe_to_string(safe) =~ "test-class"
end
end
end

View file

@ -0,0 +1,46 @@
defmodule AppWeb.LiveHelpersText do
use AppWeb.ConnCase
import Mock
import AppWeb.LiveHelpers
describe "assign_defaults/2" do
test "sets current_user" do
{store, _store_config} = Pow.Plug.Base.store(Application.get_env(:core, :pow))
socket = %Phoenix.LiveView.Socket{endpoint: AppWeb.Endpoint}
with_mock Pow.Plug, [verify_token: fn (_, _, _, _) -> {:ok, "h3110"} end] do
with_mock store, [get: fn (_config, _token) -> {%{id: 1234}, nil} end] do
new_socket = assign_defaults(socket, %{"core_auth" => "h3ll0"})
assert %{assigns: %{current_user: %{id: 1234}}} = new_socket
end
end
end
end
describe "require_auth/1" do
test "with user" do
user = %{id: 4567}
socket =
%Phoenix.LiveView.Socket{assigns: %{current_user: user}}
|> require_auth()
assert !socket.redirected
end
test "without user" do
socket = %Phoenix.LiveView.Socket{} |> require_auth()
assert socket.redirected
end
test "without nil user" do
socket =
%Phoenix.LiveView.Socket{assigns: %{current_user: nil}}
|> require_auth()
assert socket.redirected
end
end
end

View file

@ -116,15 +116,17 @@ defmodule Legendary.Content.Post do
def maybe_put_guid(changeset) do def maybe_put_guid(changeset) do
import Legendary.Content.Router.Helpers, only: [url: 1, posts_url: 3] import Legendary.Content.Router.Helpers, only: [url: 1, posts_url: 3]
slug = changeset |> get_field(:name) guid = changeset |> get_field(:guid)
case slug do case guid do
nil -> changeset nil ->
_ ->
base = url(Legendary.CoreWeb.Endpoint) base = url(Legendary.CoreWeb.Endpoint)
slug = changeset |> get_field(:name)
changeset changeset
|> put_default(:guid, posts_url(URI.merge(base, "/pages"), :show, slug)) |> put_default(:guid, posts_url(URI.merge(base, "/pages"), :show, slug))
_ ->
changeset
end end
end end
end end

View file

@ -21,6 +21,7 @@ defmodule Legendary.Content.Sitemaps do
generate() generate()
end end
@spec generate :: :ok
def generate do def generate do
create do create do
add "", priority: 0.5, changefreq: "hourly", expires: nil add "", priority: 0.5, changefreq: "hourly", expires: nil

View file

@ -67,9 +67,7 @@ defmodule Legendary.Content.PostsController do
conn |> show_one(post, page_string) conn |> show_one(post, page_string)
end end
end end
def show(conn, %{"id" => id, "page" => page_string}) when is_list(id) do def show(conn, %{"id" => id} = params) when is_list(id), do: show(conn, Map.merge(params, %{"id" => Enum.join(id, "/")}))
show(conn, %{"id" => Enum.join(id, "/"), "page" => page_string})
end
def show(conn, %{"id" => id}), do: show(conn, %{"id" => id, "page" => "1"}) def show(conn, %{"id" => id}), do: show(conn, %{"id" => id, "page" => "1"})
defp try_static_post(conn, id) do defp try_static_post(conn, id) do

View file

@ -7,6 +7,7 @@
<atom:link href="<%= Legendary.Content.Router.Helpers.url(Legendary.CoreWeb.Endpoint) %><%= @feed_url %>" rel="self" type="application/rss+xml" /> <atom:link href="<%= Legendary.Content.Router.Helpers.url(Legendary.CoreWeb.Endpoint) %><%= @feed_url %>" rel="self" type="application/rss+xml" />
<%= for post <- @posts do %> <%= for post <- @posts do %>
<%= if unauthenticated_post?(post) do %>
<item> <item>
<title><%= post.title |> HtmlSanitizeEx.strip_tags() %></title> <title><%= post.title |> HtmlSanitizeEx.strip_tags() %></title>
<description> <description>
@ -19,6 +20,7 @@
%></pubDate> %></pubDate>
<guid isPermaLink="true"><%= post.guid %></guid> <guid isPermaLink="true"><%= post.guid %></guid>
</item> </item>
<% end %>
<% end %> <% end %>
</channel> </channel>
</rss> </rss>

View file

@ -8,10 +8,12 @@ defmodule Legendary.Content.ErrorHelpers do
@doc """ @doc """
Generates tag for inlined form input errors. Generates tag for inlined form input errors.
""" """
def error_tag(form, field) do def error_tag(form, field, opts \\ []) do
{extra_classes, _rest_opts} = Keyword.pop(opts, :class, "")
Enum.map(Keyword.get_values(form.errors, field), fn error -> Enum.map(Keyword.get_values(form.errors, field), fn error ->
content_tag(:span, translate_error(error), content_tag(:span, translate_error(error),
class: "invalid-feedback", class: "invalid-feedback #{extra_classes}",
phx_feedback_for: input_id(form, field) phx_feedback_for: input_id(form, field)
) )
end) end)

View file

@ -1,71 +1,10 @@
defmodule Legendary.Content.FeedsView do defmodule Legendary.Content.FeedsView do
use Legendary.Content, :view use Legendary.Content, :view
use Phoenix.HTML use Phoenix.HTML
alias Phoenix.HTML
alias Phoenix.HTML.Tag
import Legendary.Content.LayoutView, only: [title: 3, excerpt: 3] import Legendary.Content.LayoutView, only: [title: 3, excerpt: 3]
def gravatar_url_for_email(email) do def unauthenticated_post?(post) do
email
|> Kernel.||("noreply@example.com")
|> String.trim()
|> String.downcase()
|> (&(:crypto.hash(:md5, &1))).()
|> Base.encode16()
|> String.downcase()
|> (&("https://www.gravatar.com/avatar/#{&1}")).()
end
def auto_paragraph_tags(string) do
string
|> Kernel.||("")
|> String.split(["\n\n", "\r\n\r\n"], trim: true)
|> Enum.map(fn text ->
[Tag.content_tag(:p, text |> HTML.raw(), []), ?\n]
end)
|> HTML.html_escape()
end
def post_class(post) do
sticky =
if post.sticky do
"sticky"
end
"post post-#{post.id} #{sticky}"
end
def post_topmatter(conn, post) do
author =
post.author ||
%Legendary.Auth.User{
email: "example@example.org",
display_name: "Anonymous",
homepage_url: "#"
}
assigns = %{post: post, author: author, conn: conn}
~E"""
<% _ = assigns # suppress unused assigns warning %>
<div class="Comment-topmatter">
<h4>
<%= link to: author.homepage_url || "#", rel: "author", class: "p-author h-card" do %>
<%= author.display_name %>
<%= img_tag gravatar_url_for_email(author.email), alt: "Photo of #{author.display_name}", class: "Gravatar u-photo" %>
<% end %>
</h4>
<h5>
<%= link to: Routes.posts_path(conn, :show, post) do %>
<time class="dt-published" datetime="<%= post.post %>">
<%= post.post |> Timex.format!("%F", :strftime) %>
</time>
<% end %>
</h5>
</div>
"""
end
def unauthenticated_post?(_conn, post) do
post.password == nil || String.length(post.password) == 0 post.password == nil || String.length(post.password) == 0
end end
end end

View file

@ -45,8 +45,32 @@ defmodule Legendary.Content.PostsTest do
assert %Post{} = Posts.get_post_with_drafts!(Integer.to_string(id)) assert %Post{} = Posts.get_post_with_drafts!(Integer.to_string(id))
end end
test "update_posts/2", %{public_post: post} do describe "update_posts/2" do
assert {:ok, %Post{content: "boop"}} = Posts.update_posts(post, %{content: "boop"}) setup do
post_with_guid =
%Post{
title: "Post with guid",
name: "post-with-guid",
status: "publish",
type: "post",
guid: "/beep",
date: ~N[2020-01-01T00:00:00],
}
|> Repo.insert!()
%{
post_with_guid: post_with_guid
}
end
test "with no guid", %{public_post: post} do
assert {:ok, %Post{content: "boop"}} = Posts.update_posts(post, %{content: "boop"})
end
test "with an existing guid", %{post_with_guid: post} do
revised_post = Posts.update_posts(post, %{content: "boop"})
assert {:ok, %Post{content: "boop", guid: "/beep"}} = revised_post
end
end end
test "delete_posts/1", %{public_post: post} do test "delete_posts/1", %{public_post: post} do

View file

@ -0,0 +1,38 @@
defmodule Legendary.Content.SitemapStorageTest do
use Legendary.Content.DataCase
import Legendary.Content.SitemapStorage
alias Sitemap.Location
alias Legendary.Content.{Post, Repo}
test "creates a post with the content" do
data = "<hello />"
content = data |> :zlib.gzip() |> Base.encode64
write(:file, data)
path = Location.filename(:file)
post = from(p in Post, where: p.name == ^path) |> Repo.one()
assert post.content == content
end
test "updates an existing sitemap" do
path = Location.filename(:file)
%Post{
content: "<hello />" |> :zlib.gzip() |> Base.encode64,
name: path
}
|> Repo.insert!()
new_data = "<world />"
new_content = new_data |> :zlib.gzip() |> Base.encode64
write(:file, new_data)
post = from(p in Post, where: p.name == ^path) |> Repo.one()
assert post.content == new_content
end
end

View file

@ -0,0 +1,78 @@
defmodule Legendary.Content.SitemapsTest do
use Legendary.Content.DataCase
alias Legendary.Content.{Post, Repo}
import Mock
import Legendary.Content.Sitemaps
@xml ~s(
<?xml version="1.0" encoding="UTF-8"?>
<urlset
xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9
http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"
xmlns='http://www.sitemaps.org/schemas/sitemap/0.9'
xmlns:geo='http://www.google.com/geo/schemas/sitemap/1.0'
xmlns:news='http://www.google.com/schemas/sitemap-news/0.9'
xmlns:image='http://www.google.com/schemas/sitemap-image/1.1'
xmlns:video='http://www.google.com/schemas/sitemap-video/1.1'
xmlns:mobile='http://www.google.com/schemas/sitemap-mobile/1.0'
xmlns:pagemap='http://www.google.com/schemas/sitemap-pagemap/1.0'
xmlns:xhtml='http://www.w3.org/1999/xhtml'
>
<url>
<loc>https://localhost</loc>
<lastmod>2021-08-19T21:44:37Z</lastmod>
<changefreq>hourly</changefreq>
<priority>0.5</priority>
</url><url>
<loc>https://localhost/public-post</loc>
<lastmod>2021-08-19T21:44:37Z</lastmod>
<changefreq>hourly</changefreq>
<priority>0.5</priority>
</url><url>
<loc>https://localhost/public-post?page=2</loc>
<lastmod>2021-08-19T21:44:37Z</lastmod>
<changefreq>hourly</changefreq>
<priority>0.5</priority>
</url></urlset>
)
describe "generate/0" do
setup do
public_post =
%Post{
title: "Public post",
name: "public-post",
status: "publish",
type: "post",
date: ~N[2020-01-01T00:00:00],
content: """
Page 1
<!--nextpage-->
Page 2
"""
}
|> Repo.insert!()
%{
public_post: public_post,
}
end
test "generates results" do
with_mock Sitemap.Funcs, [
iso8601: fn -> "2021-08-19T21:44:37Z" end,
iso8601: & &1,
eraser: fn (elm) -> passthrough([elm]) end
] do
with_mock Legendary.Content.SitemapStorage, [write: fn (_name, _data) -> :ok end] do
assert :ok = perform(%{})
assert_called Legendary.Content.SitemapStorage.write(:file, String.trim(@xml))
end
end
end
end
end

View file

@ -1,7 +1,7 @@
defmodule Legendary.Content.PostsControllerTest do defmodule Legendary.Content.PostsControllerTest do
use Legendary.Content.ConnCase use Legendary.Content.ConnCase
alias Legendary.Content.{Comment, Options, Posts, Repo, Term, TermRelationship, TermTaxonomy} alias Legendary.Content.{Comment, Options, Post, Posts, Repo, Term, TermRelationship, TermTaxonomy}
@create_attrs %{ @create_attrs %{
id: 123, id: 123,
@ -165,6 +165,21 @@ defmodule Legendary.Content.PostsControllerTest do
assert html_response(conn, 200) assert html_response(conn, 200)
end end
test "shows the post if the id has slashes", %{conn: conn} do
%Post{
name: "a/b/c",
content: "slashed id",
status: "publish",
type: "post",
date: ~N[2020-01-01T00:00:00]
}
|> Repo.insert!()
conn = get conn, Routes.nested_posts_path(conn, :show, ["a", "b", "c"])
assert html_response(conn, 200)
end
test "show a 404 if there's no match", %{conn: conn} do test "show a 404 if there's no match", %{conn: conn} do
assert_raise Phoenix.Router.NoRouteError, fn -> assert_raise Phoenix.Router.NoRouteError, fn ->
get conn, Routes.posts_path(conn, :show, "blooper") get conn, Routes.posts_path(conn, :show, "blooper")

View file

@ -0,0 +1,29 @@
defmodule Legendary.Content.ErrorHelpersTest do
use Legendary.Content.DataCase
import Phoenix.HTML, only: [safe_to_string: 1]
import Phoenix.HTML.Form, only: [form_for: 3]
import Legendary.Content.ErrorHelpers
def form do
:example
|> form_for(
"/example",
as: :test_params,
errors: [error_field: {"is an error", []}]
)
end
describe "error_tag/2" do
test "generates a span with an invalid-feedback class" do
[safe] = error_tag(form(), :error_field)
assert safe_to_string(safe) =~ "invalid-feedback"
end
test "error_tag/3" do
[safe] = error_tag(form(), :error_field, class: "test-class")
assert safe_to_string(safe) =~ "test-class"
end
end
end

View file

@ -0,0 +1,17 @@
defmodule Legendary.Content.FeedsViewTest do
use Legendary.Content.DataCase
import Legendary.Content.FeedsView
alias Legendary.Content.Post
describe "unauthenticated_post?/1" do
test "with post password" do
refute unauthenticated_post?(%Post{password: "password"})
end
test "without post password" do
assert unauthenticated_post?(%Post{})
end
end
end

View file

@ -4,6 +4,8 @@ defmodule Legendary.Content.PostsViewTest do
import Legendary.Content.PostsView import Legendary.Content.PostsView
import Phoenix.HTML, only: [safe_to_string: 1] import Phoenix.HTML, only: [safe_to_string: 1]
alias Legendary.Content.Post
test "auto_paragraph_tags/1 with nil" do test "auto_paragraph_tags/1 with nil" do
assert safe_to_string(auto_paragraph_tags(nil)) =~ "" assert safe_to_string(auto_paragraph_tags(nil)) =~ ""
end end
@ -11,4 +13,22 @@ defmodule Legendary.Content.PostsViewTest do
test "auto_paragraph_tags/1 with text" do test "auto_paragraph_tags/1 with text" do
assert safe_to_string(auto_paragraph_tags("Bloop\n\nBloop")) =~ "<p>Bloop</p>\n<p>Bloop</p>" assert safe_to_string(auto_paragraph_tags("Bloop\n\nBloop")) =~ "<p>Bloop</p>\n<p>Bloop</p>"
end end
describe "authenticated_for_post?/2" do
test "without password" do
assert authenticated_for_post?(nil, %Post{})
end
test "with post password that matches", %{conn: conn} do
with_mock Plug.Conn, [get_session: fn (_conn, :post_password) -> "password" end] do
assert authenticated_for_post?(conn, %Post{password: "password"})
end
end
test "with post password that does not match", %{conn: conn} do
with_mock Plug.Conn, [get_session: fn (_conn, :post_password) -> "password" end] do
refute authenticated_for_post?(conn, %Post{password: "password2"})
end
end
end
end end

View file

@ -1,4 +1,4 @@
defmodule AuthWeb.Pow.ControllerCallbacks do defmodule Legendary.AuthWeb.Pow.ControllerCallbacks do
@moduledoc """ @moduledoc """
Hook into Pow Controllers to provide additional framework feature. In particular, Hook into Pow Controllers to provide additional framework feature. In particular,
we disconnect any active live views when a user logs out. This will cause the we disconnect any active live views when a user logs out. This will cause the
@ -36,6 +36,7 @@ defmodule AuthWeb.Pow.ControllerCallbacks do
conn = conn =
conn conn
|> Conn.delete_session(:live_socket_id) |> Conn.delete_session(:live_socket_id)
|> Conn.delete_session(:current_user_id)
ControllerCallbacks.before_respond( ControllerCallbacks.before_respond(
Pow.Phoenix.SessionController, Pow.Phoenix.SessionController,

View file

@ -16,8 +16,12 @@ defmodule Legendary.CoreWeb.Plug.TestEndToEnd do
send_resp(conn, 200, "connection has already been checked out") send_resp(conn, 200, "connection has already been checked out")
else else
{:ok, _pid} = Agent.start_link(&checkout_shared_db_conn/0, name: :db_owner_agent) {:ok, _pid} = Agent.start_link(&checkout_shared_db_conn/0, name: :db_owner_agent)
{:ok, _} = load_test_seeds(conn) case load_test_seeds(conn) do
send_resp(conn, 200, "connection checked out") {:ok, _} ->
send_resp(conn, 200, "connection checked out")
{:error, msg} ->
send_resp(conn, 500, msg)
end
end end
end end
@ -40,7 +44,10 @@ defmodule Legendary.CoreWeb.Plug.TestEndToEnd do
Ecto.Repo.all_running() Ecto.Repo.all_running()
|> Enum.map(fn repo -> |> Enum.map(fn repo ->
:ok = Ecto.Adapters.SQL.Sandbox.checkout(repo, ownership_timeout: :infinity) :ok = Ecto.Adapters.SQL.Sandbox.checkout(repo, ownership_timeout: :infinity)
:ok = Ecto.Adapters.SQL.Sandbox.mode(repo, {:shared, self()}) case Ecto.Adapters.SQL.Sandbox.mode(repo, {:shared, self()}) do
:ok -> :ok
:already_shared -> :ok
end
end) end)
end end
@ -59,14 +66,16 @@ defmodule Legendary.CoreWeb.Plug.TestEndToEnd do
{:app, {:ok, app}} <- {:app, Map.fetch(conn.body_params, "app")}, {:app, {:ok, app}} <- {:app, Map.fetch(conn.body_params, "app")},
true <- String.match?(seed_set, @valid_seed_set_characters), true <- String.match?(seed_set, @valid_seed_set_characters),
true <- String.match?(app, @valid_app_characters) do true <- String.match?(app, @valid_app_characters) do
seed_path = "apps/#{app}/test/seed_sets/#{seed_set}.exs"
project_base = Path.expand(Path.join(__DIR__, "../../../../.."))
seed_path = Path.join(project_base, "apps/#{app}/test/seed_sets/#{seed_set}.exs")
try do try do
{result, _} = Code.eval_file(seed_path) {result, _} = Code.eval_file(seed_path)
{:ok, result} {:ok, result}
rescue rescue
e in Code.LoadError -> e in Code.LoadError ->
{:error, "could not load a seed set at #{seed_path}: #{e}"} {:error, e.message}
end end
else else
{:app, :error} -> {:error, "app parameter is required if seed set is set"} {:app, :error} -> {:error, "app parameter is required if seed set is set"}

View file

@ -31,4 +31,8 @@ defmodule Legendary.CoreWeb.Router do
live_dashboard "/dashboard", metrics: Legendary.CoreWeb.Telemetry live_dashboard "/dashboard", metrics: Legendary.CoreWeb.Telemetry
end end
end end
if Mix.env() in [:e2e, :test] do
forward("/end-to-end", Legendary.CoreWeb.Plug.TestEndToEnd, otp_app: :app)
end
end end

View file

@ -48,7 +48,7 @@ defmodule Legendary.CoreWeb.Helpers do
{type, rest_opts} = Keyword.pop(opts, :type, input_type(f, field)) {type, rest_opts} = Keyword.pop(opts, :type, input_type(f, field))
{classes, rest_opts} = Keyword.pop(rest_opts, :class, default_classes_for_type(type)) {classes, rest_opts} = Keyword.pop(rest_opts, :class, default_classes_for_type(type))
{label_text, rest_opts} = Keyword.pop(rest_opts, :label) {label_text, rest_opts} = Keyword.pop(rest_opts, :label)
{input_helper, _rest_opts} = Keyword.pop(rest_opts, :input_helper, input_type(f, field)) {input_helper, _rest_opts} = Keyword.pop(rest_opts, :input_helper, type)
error_classes = error_classes =
if Keyword.get_values(f.errors, field) |> Enum.any?() do if Keyword.get_values(f.errors, field) |> Enum.any?() do

View file

@ -9,11 +9,13 @@ defmodule Mix.Tasks.Legendary.CreateAdmin do
alias Ecto.Changeset alias Ecto.Changeset
@shortdoc "Create an admin user." @shortdoc "Create an admin user."
def run(_) do def run(args) do
Application.ensure_all_started(:core) Application.ensure_all_started(:core)
email = ExPrompt.string_required("Email: ") {switches, _, _} = OptionParser.parse(args, strict: [email: :string, password: :string])
password = ExPrompt.password("Password: ")
email = Keyword.get_lazy(switches, :email, fn -> ExPrompt.string_required("Email: ") end)
password = Keyword.get_lazy(switches, :password, fn -> ExPrompt.password("Password: ") end)
params = %{ params = %{
email: email, email: email,
@ -27,7 +29,7 @@ defmodule Mix.Tasks.Legendary.CreateAdmin do
|> Repo.insert!() |> Repo.insert!()
end end
def maybe_confirm_email(changeset) do defp maybe_confirm_email(changeset) do
field_list = User.__schema__(:fields) field_list = User.__schema__(:fields)
case Enum.any?(field_list, &(&1 == :email_confirmed_at)) do case Enum.any?(field_list, &(&1 == :email_confirmed_at)) do

View file

@ -0,0 +1,33 @@
defmodule Legendary.AuthWeb.Pow.ControllerCallbacksTest do
use Legendary.CoreWeb.ConnCase, async: true
import Legendary.AuthWeb.Pow.ControllerCallbacks
import Plug.Conn, only: [assign: 3, get_session: 2]
alias Legendary.Auth.User
describe "before_respond/4" do
setup %{conn: conn} do
conn =
conn
|> init_test_session([])
|> assign(:current_user, %User{id: 123})
%{conn: conn}
end
test "sets the live_socket_id in session upon sign in", %{conn: conn} do
{:ok, conn} = before_respond(Pow.Phoenix.SessionController, :create, {:ok, conn}, [])
assert get_session(conn, "live_socket_id") == "users_sockets:123"
assert get_session(conn, "current_user_id") == 123
end
test "removes the live_socket_id and broadcasts a disconnect signal upon sign out", %{conn: conn, } do
{:ok, conn} = before_respond(Pow.Phoenix.SessionController, :delete, {:ok, conn}, [])
assert get_session(conn, "live_socket_id") == nil
assert get_session(conn, "current_user_id") == nil
end
end
end

View file

@ -1,8 +1,15 @@
defmodule Legendary.AuthWeb.HelpersTest do defmodule Legendary.AuthWeb.HelpersTest do
use Legendary.CoreWeb.ConnCase use Legendary.CoreWeb.ConnCase, async: true
import Legendary.AuthWeb.Helpers import Legendary.AuthWeb.Helpers
alias Phoenix.LiveView.Socket
describe "current_user/1" do
test "can get a user from the assigns in a socket", do: assert current_user(%Socket{assigns: %{current_user: %{id: 867}}}).id == 867
test "can get a user from the __assigns__ in a socket", do: assert current_user(%Socket{assigns: %{__assigns__: %{current_user: %{id: 867}}}}).id == 867
end
describe "has_role?/2" do describe "has_role?/2" do
test "with a user", %{conn: conn} do test "with a user", %{conn: conn} do
conn = conn =

View file

@ -1,5 +1,5 @@
defmodule Legendary.AuthWeb.Plugs.RequireAdminTest do defmodule Legendary.AuthWeb.Plugs.RequireAdminTest do
use Legendary.CoreWeb.ConnCase use Legendary.CoreWeb.ConnCase, async: true
alias Legendary.AuthWeb.Plugs.RequireAdmin alias Legendary.AuthWeb.Plugs.RequireAdmin
alias Legendary.Auth.User alias Legendary.Auth.User

View file

@ -0,0 +1,38 @@
defmodule Legendary.CoreWeb.Plug.TestEndToEndTest do
use Legendary.CoreWeb.ConnCase
test "/db/setup can check out a connection", %{conn: conn} do
conn = post conn, "/end-to-end/db/setup", %{seed_set: "test_end_to_end_test", app: "core"}
assert response(conn, 200) =~ "connection checked out"
end
test "/db/setup twice is a no-op", %{conn: conn} do
conn = post conn, "/end-to-end/db/setup", %{seed_set: "test_end_to_end_test", app: "core"}
conn = post conn, "/end-to-end/db/setup", %{seed_set: "test_end_to_end_test", app: "core"}
assert response(conn, 200) =~ "connection has already been checked out"
end
test "/db/setup with no-existent seed set is an error", %{conn: conn} do
conn = post conn, "/end-to-end/db/setup", %{seed_set: "oops", app: "core"}
assert response(conn, 500) =~ "could not load"
assert response(conn, 500) =~ "oops.exs"
end
test "/db/teardown can check in a connection", %{conn: conn} do
conn = post conn, "/end-to-end/db/setup", %{seed_set: "test_end_to_end_test", app: "core"}
conn = post conn, "/end-to-end/db/teardown"
assert response(conn, 200) =~ "checked in database connection"
end
test "/db/teardown checking in twice is a no-op", %{conn: conn} do
conn = post conn, "/end-to-end/db/setup", %{seed_set: "test_end_to_end_test", app: "core"}
conn = post conn, "/end-to-end/db/teardown"
conn = post conn, "/end-to-end/db/teardown"
assert response(conn, 200) =~ "connection has already been checked back in"
end
end

View file

@ -1,5 +1,5 @@
defmodule Legendary.CoreWeb.HelpersTest do defmodule Legendary.CoreWeb.HelpersTest do
use Legendary.CoreWeb.ConnCase use Legendary.CoreWeb.ConnCase, async: true
import Legendary.CoreWeb.Helpers import Legendary.CoreWeb.Helpers
import Ecto.Changeset, import Ecto.Changeset,
@ -35,6 +35,17 @@ defmodule Legendary.CoreWeb.HelpersTest do
changeset changeset
end end
describe "current_user?/1" do
test "returns nil for a conn with no user", %{conn: conn}, do: refute conn |> setup_pow() |> current_user()
test "returns a user for a conn with a user", %{conn: conn} do
conn =
conn
|> setup_user(id: 456)
assert current_user(conn).id == 456
end
end
describe "has_role?/2" do describe "has_role?/2" do
test "with a user", %{conn: conn} do test "with a user", %{conn: conn} do
conn = conn =
@ -105,6 +116,87 @@ defmodule Legendary.CoreWeb.HelpersTest do
assert markup =~ "<label" assert markup =~ "<label"
end end
test "styled_input for date_select" do
markup =
form()
|> styled_input(:no_error_field, type: :date_select)
|> safe_to_string()
assert markup =~ "<select"
end
describe "styled_button/1" do
test "makes a button with content" do
markup =
styled_button("Push Me")
|> safe_to_string()
assert markup =~ ">Push Me"
assert markup =~ "<button"
end
end
describe "styled_button_link/2" do
test "makes a link with content and attributes" do
markup =
styled_button_link("Push Me", to: "#anchor")
|> safe_to_string()
assert markup =~ ">Push Me"
assert markup =~ "<a"
assert markup =~ ~s(href="#anchor")
end
end
describe "paginator/3" do
def reflector(range, current), do: {range, current}
test "works with only one page" do
assert paginator(1..1, 1, &reflector/2) == [{1..1, 1}]
end
test "works with two pages" do
assert paginator(1..2, 1, &reflector/2) == [{1..2, 1}, {1..2, 2}]
end
test "works with three pages" do
assert paginator(1..3, 3, &reflector/2) == [{1..3, 1}, {1..3, 2}, {1..3, 3}]
end
test "works with many pages" do
assert paginator(1..10, 7, &reflector/2) == [{1..10, 1}, {1..10, 6}, {1..10, 7}, {1..10, 8}, {1..10, 10}]
end
end
describe "group_rounding_class/3" do
test "handles the only element", do: assert group_rounding_class(1..1, 1) == "rounded-l rounded-r"
test "handles the first element", do: assert group_rounding_class(1..2, 1) == "rounded-l"
test "handles the last element", do: assert group_rounding_class(1..2, 2) == "rounded-r"
test "handles middle elements", do: assert group_rounding_class(1..3, 2) == ""
test "handles custom classes", do: assert group_rounding_class(1..3, 1, ["custom", "", ""]) == "custom"
end
describe "floating_form/3" do
test "includes title and content" do
markup =
floating_form("Test Title", %{action: "test"}, do: "Test Content")
|> safe_to_string()
assert markup =~ "Test Title"
assert markup =~ "Test Content"
end
end
describe "floating_page_wrapper/1" do
test "includes content" do
markup =
floating_page_wrapper(do: "Test Content")
|> safe_to_string()
assert markup =~ "Test Content"
end
end
test "pow_extension_enabled?/1" do test "pow_extension_enabled?/1" do
assert pow_extension_enabled?(PowEmailConfirmation) == true assert pow_extension_enabled?(PowEmailConfirmation) == true
assert pow_extension_enabled?(:donkdonk) == false assert pow_extension_enabled?(:donkdonk) == false

View file

@ -0,0 +1,17 @@
defmodule Mix.Tasks.Legendary.CreateAdminTest do
use Legendary.Core.DataCase
import Mix.Tasks.Legendary.CreateAdmin
alias Legendary.Auth.User
describe "run/1" do
test "creates an admin user" do
run(["--email=test@example.com", "--password=openseasame"])
user = from(u in User, where: u.email == "test@example.com") |> Repo.one()
assert %User{email: "test@example.com"} = user
end
end
end

View file

@ -0,0 +1,14 @@
defmodule Legendary.Core.SharedDBConnectionPoolTest do
use Legendary.Core.DataCase
import Legendary.Core.SharedDBConnectionPool
test "child_spec/2" do
spec = child_spec({Postgrex.Protocol, []})
assert %{id: Legendary.Core.SharedDBConnectionPool, start: start} = spec
assert {Legendary.Core.SharedDBConnectionPool, :start_link, [{Postgrex.Protocol, opts}]} = start
assert [{:name, hashed_name}] = opts
assert Atom.to_string(hashed_name) =~ "Legendary.Core.SharedDBConnectionPool."
end
end

View file

@ -28,6 +28,22 @@ defmodule Legendary.CoreWeb.ConnCase do
# The default endpoint for testing # The default endpoint for testing
@endpoint Legendary.CoreWeb.Endpoint @endpoint Legendary.CoreWeb.Endpoint
def setup_pow(conn) do
conn
|> Pow.Plug.put_config(current_user_assigns_key: :current_user)
end
def setup_user(conn, attrs \\ []) do
user =
attrs
|> Enum.into(%{})
|> (& struct(Legendary.Auth.User, &1)).()
conn
|> setup_pow()
|> Pow.Plug.assign_current_user(user, [])
end
end end
end end

View file

@ -1,24 +0,0 @@
defmodule Legendary.Core.Cluster.EmptyClusterStrategy do
@moduledoc """
A libcluster nil strategy that always returns no nodes.
"""
use GenServer
use Cluster.Strategy
alias Cluster.Strategy.State
def start_link([%State{} = state]) do
new_state = %State{state | :meta => []}
GenServer.start_link(__MODULE__, [new_state])
end
@impl true
def init([state]) do
{:ok, state, :infinity}
end
@impl true
def handle_info(_, state) do
{:noreply, state, :infinity}
end
end

View file

@ -40,7 +40,7 @@ config :core, :pow,
user: Legendary.Auth.User, user: Legendary.Auth.User,
repo: Legendary.Core.Repo, repo: Legendary.Core.Repo,
extensions: [PowEmailConfirmation, PowPersistentSession, PowResetPassword], extensions: [PowEmailConfirmation, PowPersistentSession, PowResetPassword],
controller_callbacks: AuthWeb.Pow.ControllerCallbacks, controller_callbacks: Legendary.AuthWeb.Pow.ControllerCallbacks,
mailer_backend: Legendary.AuthWeb.Pow.Mailer, mailer_backend: Legendary.AuthWeb.Pow.Mailer,
web_mailer_module: Legendary.AuthWeb, web_mailer_module: Legendary.AuthWeb,
web_module: Legendary.AuthWeb, web_module: Legendary.AuthWeb,

View file

@ -42,9 +42,4 @@ config :content, Oban, crontab: false, queues: false, plugins: false
config :logger, level: :warn config :logger, level: :warn
config :libcluster, config :libcluster, topologies: []
topologies: [
# erlang_hosts: [
# strategy: Legendary.Core.Cluster.EmptyClusterStrategy
# ]
]