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:
commit
6893fc8d23
35 changed files with 573 additions and 126 deletions
|
@ -9,9 +9,10 @@ defmodule AppWeb.LiveHelpers do
|
|||
end
|
||||
|
||||
def require_auth(socket) do
|
||||
if socket.assigns.current_user do
|
||||
case socket.assigns do
|
||||
%{current_user: user} when not is_nil(user) ->
|
||||
socket
|
||||
else
|
||||
_ ->
|
||||
redirect(socket, to: "/")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -51,7 +51,7 @@ defmodule AppWeb.Router do
|
|||
forward "/sent_emails", Bamboo.SentEmailViewerPlug
|
||||
end
|
||||
|
||||
if Mix.env() == :e2e do
|
||||
if Mix.env() in [:e2e, :test] do
|
||||
forward("/end-to-end", Legendary.CoreWeb.Plug.TestEndToEnd, otp_app: :app)
|
||||
end
|
||||
|
||||
|
|
|
@ -8,10 +8,12 @@ defmodule AppWeb.ErrorHelpers do
|
|||
@doc """
|
||||
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 ->
|
||||
content_tag(:span, translate_error(error),
|
||||
class: "invalid-feedback",
|
||||
class: "invalid-feedback #{extra_classes}",
|
||||
phx_feedback_for: input_id(form, field)
|
||||
)
|
||||
end)
|
||||
|
|
29
apps/app/test/app_web/error_helpers_test.exs
Normal file
29
apps/app/test/app_web/error_helpers_test.exs
Normal 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
|
46
apps/app/test/app_web/live_helpers_test.exs
Normal file
46
apps/app/test/app_web/live_helpers_test.exs
Normal 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
|
|
@ -116,15 +116,17 @@ defmodule Legendary.Content.Post do
|
|||
|
||||
def maybe_put_guid(changeset) do
|
||||
import Legendary.Content.Router.Helpers, only: [url: 1, posts_url: 3]
|
||||
slug = changeset |> get_field(:name)
|
||||
guid = changeset |> get_field(:guid)
|
||||
|
||||
case slug do
|
||||
nil -> changeset
|
||||
_ ->
|
||||
case guid do
|
||||
nil ->
|
||||
base = url(Legendary.CoreWeb.Endpoint)
|
||||
slug = changeset |> get_field(:name)
|
||||
|
||||
changeset
|
||||
|> put_default(:guid, posts_url(URI.merge(base, "/pages"), :show, slug))
|
||||
_ ->
|
||||
changeset
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -21,6 +21,7 @@ defmodule Legendary.Content.Sitemaps do
|
|||
generate()
|
||||
end
|
||||
|
||||
@spec generate :: :ok
|
||||
def generate do
|
||||
create do
|
||||
add "", priority: 0.5, changefreq: "hourly", expires: nil
|
|
@ -67,9 +67,7 @@ defmodule Legendary.Content.PostsController do
|
|||
conn |> show_one(post, page_string)
|
||||
end
|
||||
end
|
||||
def show(conn, %{"id" => id, "page" => page_string}) when is_list(id) do
|
||||
show(conn, %{"id" => Enum.join(id, "/"), "page" => page_string})
|
||||
end
|
||||
def show(conn, %{"id" => id} = params) when is_list(id), do: show(conn, Map.merge(params, %{"id" => Enum.join(id, "/")}))
|
||||
def show(conn, %{"id" => id}), do: show(conn, %{"id" => id, "page" => "1"})
|
||||
|
||||
defp try_static_post(conn, id) do
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
<atom:link href="<%= Legendary.Content.Router.Helpers.url(Legendary.CoreWeb.Endpoint) %><%= @feed_url %>" rel="self" type="application/rss+xml" />
|
||||
|
||||
<%= for post <- @posts do %>
|
||||
<%= if unauthenticated_post?(post) do %>
|
||||
<item>
|
||||
<title><%= post.title |> HtmlSanitizeEx.strip_tags() %></title>
|
||||
<description>
|
||||
|
@ -20,5 +21,6 @@
|
|||
<guid isPermaLink="true"><%= post.guid %></guid>
|
||||
</item>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</channel>
|
||||
</rss>
|
||||
|
|
|
@ -8,10 +8,12 @@ defmodule Legendary.Content.ErrorHelpers do
|
|||
@doc """
|
||||
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 ->
|
||||
content_tag(:span, translate_error(error),
|
||||
class: "invalid-feedback",
|
||||
class: "invalid-feedback #{extra_classes}",
|
||||
phx_feedback_for: input_id(form, field)
|
||||
)
|
||||
end)
|
||||
|
|
|
@ -1,71 +1,10 @@
|
|||
defmodule Legendary.Content.FeedsView do
|
||||
use Legendary.Content, :view
|
||||
use Phoenix.HTML
|
||||
alias Phoenix.HTML
|
||||
alias Phoenix.HTML.Tag
|
||||
|
||||
import Legendary.Content.LayoutView, only: [title: 3, excerpt: 3]
|
||||
|
||||
def gravatar_url_for_email(email) 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
|
||||
def unauthenticated_post?(post) do
|
||||
post.password == nil || String.length(post.password) == 0
|
||||
end
|
||||
end
|
||||
|
|
|
@ -45,10 +45,34 @@ defmodule Legendary.Content.PostsTest do
|
|||
assert %Post{} = Posts.get_post_with_drafts!(Integer.to_string(id))
|
||||
end
|
||||
|
||||
test "update_posts/2", %{public_post: post} do
|
||||
describe "update_posts/2" do
|
||||
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
|
||||
|
||||
test "delete_posts/1", %{public_post: post} do
|
||||
assert Enum.count(Posts.list_posts()) == 1
|
||||
assert {:ok, _} = Posts.delete_posts(post)
|
||||
|
|
38
apps/content/test/content/sitemap_storage_test.exs
Normal file
38
apps/content/test/content/sitemap_storage_test.exs
Normal 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
|
78
apps/content/test/content/sitemaps_test.exs
Normal file
78
apps/content/test/content/sitemaps_test.exs
Normal 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
|
|
@ -1,7 +1,7 @@
|
|||
defmodule Legendary.Content.PostsControllerTest do
|
||||
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 %{
|
||||
id: 123,
|
||||
|
@ -165,6 +165,21 @@ defmodule Legendary.Content.PostsControllerTest do
|
|||
assert html_response(conn, 200)
|
||||
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
|
||||
assert_raise Phoenix.Router.NoRouteError, fn ->
|
||||
get conn, Routes.posts_path(conn, :show, "blooper")
|
||||
|
|
29
apps/content/test/content_web/views/error_helpers_test.exs
Normal file
29
apps/content/test/content_web/views/error_helpers_test.exs
Normal 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
|
17
apps/content/test/content_web/views/feeds_view_test.exs
Normal file
17
apps/content/test/content_web/views/feeds_view_test.exs
Normal 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
|
|
@ -4,6 +4,8 @@ defmodule Legendary.Content.PostsViewTest do
|
|||
import Legendary.Content.PostsView
|
||||
import Phoenix.HTML, only: [safe_to_string: 1]
|
||||
|
||||
alias Legendary.Content.Post
|
||||
|
||||
test "auto_paragraph_tags/1 with nil" do
|
||||
assert safe_to_string(auto_paragraph_tags(nil)) =~ ""
|
||||
end
|
||||
|
@ -11,4 +13,22 @@ defmodule Legendary.Content.PostsViewTest 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>"
|
||||
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
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
defmodule AuthWeb.Pow.ControllerCallbacks do
|
||||
defmodule Legendary.AuthWeb.Pow.ControllerCallbacks do
|
||||
@moduledoc """
|
||||
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
|
||||
|
@ -36,6 +36,7 @@ defmodule AuthWeb.Pow.ControllerCallbacks do
|
|||
conn =
|
||||
conn
|
||||
|> Conn.delete_session(:live_socket_id)
|
||||
|> Conn.delete_session(:current_user_id)
|
||||
|
||||
ControllerCallbacks.before_respond(
|
||||
Pow.Phoenix.SessionController,
|
||||
|
|
|
@ -16,8 +16,12 @@ defmodule Legendary.CoreWeb.Plug.TestEndToEnd do
|
|||
send_resp(conn, 200, "connection has already been checked out")
|
||||
else
|
||||
{: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
|
||||
{:ok, _} ->
|
||||
send_resp(conn, 200, "connection checked out")
|
||||
{:error, msg} ->
|
||||
send_resp(conn, 500, msg)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -40,7 +44,10 @@ defmodule Legendary.CoreWeb.Plug.TestEndToEnd do
|
|||
Ecto.Repo.all_running()
|
||||
|> Enum.map(fn repo ->
|
||||
: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
|
||||
|
||||
|
@ -59,14 +66,16 @@ defmodule Legendary.CoreWeb.Plug.TestEndToEnd do
|
|||
{:app, {:ok, app}} <- {:app, Map.fetch(conn.body_params, "app")},
|
||||
true <- String.match?(seed_set, @valid_seed_set_characters),
|
||||
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
|
||||
{result, _} = Code.eval_file(seed_path)
|
||||
{:ok, result}
|
||||
rescue
|
||||
e in Code.LoadError ->
|
||||
{:error, "could not load a seed set at #{seed_path}: #{e}"}
|
||||
{:error, e.message}
|
||||
end
|
||||
else
|
||||
{:app, :error} -> {:error, "app parameter is required if seed set is set"}
|
||||
|
|
|
@ -31,4 +31,8 @@ defmodule Legendary.CoreWeb.Router do
|
|||
live_dashboard "/dashboard", metrics: Legendary.CoreWeb.Telemetry
|
||||
end
|
||||
end
|
||||
|
||||
if Mix.env() in [:e2e, :test] do
|
||||
forward("/end-to-end", Legendary.CoreWeb.Plug.TestEndToEnd, otp_app: :app)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -48,7 +48,7 @@ defmodule Legendary.CoreWeb.Helpers do
|
|||
{type, rest_opts} = Keyword.pop(opts, :type, input_type(f, field))
|
||||
{classes, rest_opts} = Keyword.pop(rest_opts, :class, default_classes_for_type(type))
|
||||
{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 =
|
||||
if Keyword.get_values(f.errors, field) |> Enum.any?() do
|
||||
|
|
|
@ -9,11 +9,13 @@ defmodule Mix.Tasks.Legendary.CreateAdmin do
|
|||
alias Ecto.Changeset
|
||||
|
||||
@shortdoc "Create an admin user."
|
||||
def run(_) do
|
||||
def run(args) do
|
||||
Application.ensure_all_started(:core)
|
||||
|
||||
email = ExPrompt.string_required("Email: ")
|
||||
password = ExPrompt.password("Password: ")
|
||||
{switches, _, _} = OptionParser.parse(args, strict: [email: :string, password: :string])
|
||||
|
||||
email = Keyword.get_lazy(switches, :email, fn -> ExPrompt.string_required("Email: ") end)
|
||||
password = Keyword.get_lazy(switches, :password, fn -> ExPrompt.password("Password: ") end)
|
||||
|
||||
params = %{
|
||||
email: email,
|
||||
|
@ -27,7 +29,7 @@ defmodule Mix.Tasks.Legendary.CreateAdmin do
|
|||
|> Repo.insert!()
|
||||
end
|
||||
|
||||
def maybe_confirm_email(changeset) do
|
||||
defp maybe_confirm_email(changeset) do
|
||||
field_list = User.__schema__(:fields)
|
||||
|
||||
case Enum.any?(field_list, &(&1 == :email_confirmed_at)) do
|
||||
|
|
33
apps/core/test/auth_web/controller_callback_test.exs
Normal file
33
apps/core/test/auth_web/controller_callback_test.exs
Normal 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
|
|
@ -1,8 +1,15 @@
|
|||
defmodule Legendary.AuthWeb.HelpersTest do
|
||||
use Legendary.CoreWeb.ConnCase
|
||||
use Legendary.CoreWeb.ConnCase, async: true
|
||||
|
||||
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
|
||||
test "with a user", %{conn: conn} do
|
||||
conn =
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
defmodule Legendary.AuthWeb.Plugs.RequireAdminTest do
|
||||
use Legendary.CoreWeb.ConnCase
|
||||
use Legendary.CoreWeb.ConnCase, async: true
|
||||
|
||||
alias Legendary.AuthWeb.Plugs.RequireAdmin
|
||||
alias Legendary.Auth.User
|
||||
|
|
38
apps/core/test/core_web/plugs/test_end_to_end_test.exs
Normal file
38
apps/core/test/core_web/plugs/test_end_to_end_test.exs
Normal 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
|
|
@ -1,5 +1,5 @@
|
|||
defmodule Legendary.CoreWeb.HelpersTest do
|
||||
use Legendary.CoreWeb.ConnCase
|
||||
use Legendary.CoreWeb.ConnCase, async: true
|
||||
|
||||
import Legendary.CoreWeb.Helpers
|
||||
import Ecto.Changeset,
|
||||
|
@ -35,6 +35,17 @@ defmodule Legendary.CoreWeb.HelpersTest do
|
|||
changeset
|
||||
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
|
||||
test "with a user", %{conn: conn} do
|
||||
conn =
|
||||
|
@ -105,6 +116,87 @@ defmodule Legendary.CoreWeb.HelpersTest do
|
|||
assert markup =~ "<label"
|
||||
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
|
||||
assert pow_extension_enabled?(PowEmailConfirmation) == true
|
||||
assert pow_extension_enabled?(:donkdonk) == false
|
||||
|
|
17
apps/core/test/mix/tasks/create_admin_test.exs
Normal file
17
apps/core/test/mix/tasks/create_admin_test.exs
Normal 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
|
0
apps/core/test/seed_sets/test_end_to_end_test.exs
Normal file
0
apps/core/test/seed_sets/test_end_to_end_test.exs
Normal file
14
apps/core/test/shared_db_connection_pool_test.exs
Normal file
14
apps/core/test/shared_db_connection_pool_test.exs
Normal 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
|
|
@ -28,6 +28,22 @@ defmodule Legendary.CoreWeb.ConnCase do
|
|||
|
||||
# The default endpoint for testing
|
||||
@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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -40,7 +40,7 @@ config :core, :pow,
|
|||
user: Legendary.Auth.User,
|
||||
repo: Legendary.Core.Repo,
|
||||
extensions: [PowEmailConfirmation, PowPersistentSession, PowResetPassword],
|
||||
controller_callbacks: AuthWeb.Pow.ControllerCallbacks,
|
||||
controller_callbacks: Legendary.AuthWeb.Pow.ControllerCallbacks,
|
||||
mailer_backend: Legendary.AuthWeb.Pow.Mailer,
|
||||
web_mailer_module: Legendary.AuthWeb,
|
||||
web_module: Legendary.AuthWeb,
|
||||
|
|
|
@ -42,9 +42,4 @@ config :content, Oban, crontab: false, queues: false, plugins: false
|
|||
|
||||
config :logger, level: :warn
|
||||
|
||||
config :libcluster,
|
||||
topologies: [
|
||||
# erlang_hosts: [
|
||||
# strategy: Legendary.Core.Cluster.EmptyClusterStrategy
|
||||
# ]
|
||||
]
|
||||
config :libcluster, topologies: []
|
||||
|
|
Loading…
Reference in a new issue