diff --git a/Dockerfile b/Dockerfile
index bff879f2..93887adc 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -29,7 +29,7 @@ RUN mix deps.get
# Leave off here so that we can built assets and compile the elixir app in parallel
-FROM node:16.6.2 AS asset-builder
+FROM node:16.8.0 AS asset-builder
# Build assets in a node container
ADD ./apps/app/assets/ /root/app/apps/app/assets/
diff --git a/apps/admin/mix.exs b/apps/admin/mix.exs
index 89c799f7..1a9bc6b9 100644
--- a/apps/admin/mix.exs
+++ b/apps/admin/mix.exs
@@ -1,7 +1,7 @@
defmodule Legendary.Admin.MixProject do
use Mix.Project
- @version "4.0.2"
+ @version "4.0.3"
def project do
[
diff --git a/apps/app/lib/app_web/live_helpers.ex b/apps/app/lib/app_web/live_helpers.ex
index f225607d..1ea6b60c 100644
--- a/apps/app/lib/app_web/live_helpers.ex
+++ b/apps/app/lib/app_web/live_helpers.ex
@@ -9,10 +9,11 @@ defmodule AppWeb.LiveHelpers do
end
def require_auth(socket) do
- if socket.assigns.current_user do
- socket
- else
- redirect(socket, to: "/")
+ case socket.assigns do
+ %{current_user: user} when not is_nil(user) ->
+ socket
+ _ ->
+ redirect(socket, to: "/")
end
end
diff --git a/apps/app/lib/app_web/router.ex b/apps/app/lib/app_web/router.ex
index f00ab155..fbb3a9cf 100644
--- a/apps/app/lib/app_web/router.ex
+++ b/apps/app/lib/app_web/router.ex
@@ -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
diff --git a/apps/app/lib/app_web/views/error_helpers.ex b/apps/app/lib/app_web/views/error_helpers.ex
index 2e891a28..e09b4a05 100644
--- a/apps/app/lib/app_web/views/error_helpers.ex
+++ b/apps/app/lib/app_web/views/error_helpers.ex
@@ -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)
diff --git a/apps/app/test/app_web/error_helpers_test.exs b/apps/app/test/app_web/error_helpers_test.exs
new file mode 100644
index 00000000..5b98bdbf
--- /dev/null
+++ b/apps/app/test/app_web/error_helpers_test.exs
@@ -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
diff --git a/apps/app/test/app_web/live_helpers_test.exs b/apps/app/test/app_web/live_helpers_test.exs
new file mode 100644
index 00000000..e4bdad6b
--- /dev/null
+++ b/apps/app/test/app_web/live_helpers_test.exs
@@ -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
diff --git a/apps/content/lib/content/post.ex b/apps/content/lib/content/post.ex
index 97901ee0..b06803dc 100644
--- a/apps/content/lib/content/post.ex
+++ b/apps/content/lib/content/post.ex
@@ -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
diff --git a/apps/content/lib/content/sitemap.ex b/apps/content/lib/content/sitemaps.ex
similarity index 98%
rename from apps/content/lib/content/sitemap.ex
rename to apps/content/lib/content/sitemaps.ex
index e2eeda00..6ab6bb47 100644
--- a/apps/content/lib/content/sitemap.ex
+++ b/apps/content/lib/content/sitemaps.ex
@@ -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
diff --git a/apps/content/lib/content_web/controllers/posts_controller.ex b/apps/content/lib/content_web/controllers/posts_controller.ex
index 65857566..8add7dfe 100644
--- a/apps/content/lib/content_web/controllers/posts_controller.ex
+++ b/apps/content/lib/content_web/controllers/posts_controller.ex
@@ -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
diff --git a/apps/content/lib/content_web/templates/feeds/index.rss.eex b/apps/content/lib/content_web/templates/feeds/index.rss.eex
index d5cf78a6..89f9fac6 100644
--- a/apps/content/lib/content_web/templates/feeds/index.rss.eex
+++ b/apps/content/lib/content_web/templates/feeds/index.rss.eex
@@ -7,6 +7,7 @@
<%= for post <- @posts do %>
+ <%= if unauthenticated_post?(post) do %>
-
<%= post.title |> HtmlSanitizeEx.strip_tags() %>
@@ -19,6 +20,7 @@
%>
<%= post.guid %>
+ <% end %>
<% end %>
diff --git a/apps/content/lib/content_web/views/error_helpers.ex b/apps/content/lib/content_web/views/error_helpers.ex
index 85e5ed5a..5eeeef0e 100644
--- a/apps/content/lib/content_web/views/error_helpers.ex
+++ b/apps/content/lib/content_web/views/error_helpers.ex
@@ -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)
diff --git a/apps/content/lib/content_web/views/feeds_view.ex b/apps/content/lib/content_web/views/feeds_view.ex
index b768b144..4ff3c23a 100644
--- a/apps/content/lib/content_web/views/feeds_view.ex
+++ b/apps/content/lib/content_web/views/feeds_view.ex
@@ -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 %>
-
- """
- end
-
- def unauthenticated_post?(_conn, post) do
+ def unauthenticated_post?(post) do
post.password == nil || String.length(post.password) == 0
end
end
diff --git a/apps/content/mix.exs b/apps/content/mix.exs
index 19d47e71..d0a56206 100644
--- a/apps/content/mix.exs
+++ b/apps/content/mix.exs
@@ -1,7 +1,7 @@
defmodule Legendary.Content.MixProject do
use Mix.Project
- @version "4.0.2"
+ @version "4.0.3"
def project do
[
diff --git a/apps/content/test/content/posts_test.exs b/apps/content/test/content/posts_test.exs
index 971ee9cb..26ebbf2b 100644
--- a/apps/content/test/content/posts_test.exs
+++ b/apps/content/test/content/posts_test.exs
@@ -45,8 +45,32 @@ 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
- assert {:ok, %Post{content: "boop"}} = Posts.update_posts(post, %{content: "boop"})
+ 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
diff --git a/apps/content/test/content/sitemap_storage_test.exs b/apps/content/test/content/sitemap_storage_test.exs
new file mode 100644
index 00000000..67c92a4d
--- /dev/null
+++ b/apps/content/test/content/sitemap_storage_test.exs
@@ -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 = ""
+ 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: "" |> :zlib.gzip() |> Base.encode64,
+ name: path
+ }
+ |> Repo.insert!()
+
+ new_data = ""
+ 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
diff --git a/apps/content/test/content/sitemaps_test.exs b/apps/content/test/content/sitemaps_test.exs
new file mode 100644
index 00000000..191c7446
--- /dev/null
+++ b/apps/content/test/content/sitemaps_test.exs
@@ -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(
+
+
+
+ https://localhost
+ 2021-08-19T21:44:37Z
+ hourly
+ 0.5
+
+ https://localhost/public-post
+ 2021-08-19T21:44:37Z
+ hourly
+ 0.5
+
+ https://localhost/public-post?page=2
+ 2021-08-19T21:44:37Z
+ hourly
+ 0.5
+
+)
+
+ 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
+
+ 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
diff --git a/apps/content/test/content_web/controllers/posts_controller_test.exs b/apps/content/test/content_web/controllers/posts_controller_test.exs
index 6a220703..64e0193c 100644
--- a/apps/content/test/content_web/controllers/posts_controller_test.exs
+++ b/apps/content/test/content_web/controllers/posts_controller_test.exs
@@ -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")
diff --git a/apps/content/test/content_web/views/error_helpers_test.exs b/apps/content/test/content_web/views/error_helpers_test.exs
new file mode 100644
index 00000000..39f08b0a
--- /dev/null
+++ b/apps/content/test/content_web/views/error_helpers_test.exs
@@ -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
diff --git a/apps/content/test/content_web/views/feeds_view_test.exs b/apps/content/test/content_web/views/feeds_view_test.exs
new file mode 100644
index 00000000..5e1440c4
--- /dev/null
+++ b/apps/content/test/content_web/views/feeds_view_test.exs
@@ -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
diff --git a/apps/content/test/content_web/views/posts_view_test.exs b/apps/content/test/content_web/views/posts_view_test.exs
index a3ecc396..789900f8 100644
--- a/apps/content/test/content_web/views/posts_view_test.exs
+++ b/apps/content/test/content_web/views/posts_view_test.exs
@@ -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")) =~ "Bloop
\nBloop
"
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
diff --git a/apps/core/lib/auth_web/controller_callbacks.ex b/apps/core/lib/auth_web/controller_callbacks.ex
index 9b7e344a..a339b7aa 100644
--- a/apps/core/lib/auth_web/controller_callbacks.ex
+++ b/apps/core/lib/auth_web/controller_callbacks.ex
@@ -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,
diff --git a/apps/core/lib/core_web/plugs/test_end_to_end.ex b/apps/core/lib/core_web/plugs/test_end_to_end.ex
index ef6137af..2170c8c4 100644
--- a/apps/core/lib/core_web/plugs/test_end_to_end.ex
+++ b/apps/core/lib/core_web/plugs/test_end_to_end.ex
@@ -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)
- send_resp(conn, 200, "connection checked out")
+ 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"}
diff --git a/apps/core/lib/core_web/router.ex b/apps/core/lib/core_web/router.ex
index 279e6d9c..5b6bb2c7 100644
--- a/apps/core/lib/core_web/router.ex
+++ b/apps/core/lib/core_web/router.ex
@@ -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
diff --git a/apps/core/lib/core_web/views/helpers.ex b/apps/core/lib/core_web/views/helpers.ex
index f1d4fca0..cfed1753 100644
--- a/apps/core/lib/core_web/views/helpers.ex
+++ b/apps/core/lib/core_web/views/helpers.ex
@@ -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
diff --git a/apps/core/lib/mix/tasks/create_admin.ex b/apps/core/lib/mix/tasks/create_admin.ex
index cd1dbe92..58703b7f 100644
--- a/apps/core/lib/mix/tasks/create_admin.ex
+++ b/apps/core/lib/mix/tasks/create_admin.ex
@@ -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
diff --git a/apps/core/mix.exs b/apps/core/mix.exs
index c33f7177..7f89825c 100644
--- a/apps/core/mix.exs
+++ b/apps/core/mix.exs
@@ -1,7 +1,7 @@
defmodule Legendary.Core.MixProject do
use Mix.Project
- @version "4.0.2"
+ @version "4.0.3"
def project do
[
diff --git a/apps/core/test/auth_web/controller_callback_test.exs b/apps/core/test/auth_web/controller_callback_test.exs
new file mode 100644
index 00000000..50d6357b
--- /dev/null
+++ b/apps/core/test/auth_web/controller_callback_test.exs
@@ -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
diff --git a/apps/core/test/auth_web/helpers_test.exs b/apps/core/test/auth_web/helpers_test.exs
index 20bc71b1..2329a19d 100644
--- a/apps/core/test/auth_web/helpers_test.exs
+++ b/apps/core/test/auth_web/helpers_test.exs
@@ -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 =
diff --git a/apps/core/test/auth_web/plugs/require_admin_test.exs b/apps/core/test/auth_web/plugs/require_admin_test.exs
index 35d2f09f..1f67651b 100644
--- a/apps/core/test/auth_web/plugs/require_admin_test.exs
+++ b/apps/core/test/auth_web/plugs/require_admin_test.exs
@@ -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
diff --git a/apps/core/test/core_web/plugs/test_end_to_end_test.exs b/apps/core/test/core_web/plugs/test_end_to_end_test.exs
new file mode 100644
index 00000000..610c8405
--- /dev/null
+++ b/apps/core/test/core_web/plugs/test_end_to_end_test.exs
@@ -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
diff --git a/apps/core/test/core_web/views/helpers_test.exs b/apps/core/test/core_web/views/helpers_test.exs
index e0b8b6ba..a8d4b06b 100644
--- a/apps/core/test/core_web/views/helpers_test.exs
+++ b/apps/core/test/core_web/views/helpers_test.exs
@@ -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 =~ "