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 %> -
- -

- <%= 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 %> -

-
- <%= link to: Routes.posts_path(conn, :show, post) do %> - - <% end %> -
-
- """ - 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/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

\n

Bloop

" 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/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 =~ " styled_input(:no_error_field, type: :date_select) + |> safe_to_string() + + assert markup =~ " safe_to_string() + + assert markup =~ ">Push Me" + assert markup =~ " safe_to_string() + + assert markup =~ ">Push Me" + assert markup =~ " 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 diff --git a/apps/core/test/mix/tasks/create_admin_test.exs b/apps/core/test/mix/tasks/create_admin_test.exs new file mode 100644 index 00000000..87471981 --- /dev/null +++ b/apps/core/test/mix/tasks/create_admin_test.exs @@ -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 diff --git a/apps/core/test/seed_sets/test_end_to_end_test.exs b/apps/core/test/seed_sets/test_end_to_end_test.exs new file mode 100644 index 00000000..e69de29b diff --git a/apps/core/test/shared_db_connection_pool_test.exs b/apps/core/test/shared_db_connection_pool_test.exs new file mode 100644 index 00000000..ba23ba97 --- /dev/null +++ b/apps/core/test/shared_db_connection_pool_test.exs @@ -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 diff --git a/apps/core/test/support/conn_case.ex b/apps/core/test/support/conn_case.ex index 69f5c748..33eaf9f6 100644 --- a/apps/core/test/support/conn_case.ex +++ b/apps/core/test/support/conn_case.ex @@ -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 diff --git a/apps/core/test/support/empty_cluster_strategy.ex b/apps/core/test/support/empty_cluster_strategy.ex deleted file mode 100644 index 7cc5c124..00000000 --- a/apps/core/test/support/empty_cluster_strategy.ex +++ /dev/null @@ -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 diff --git a/config/config.exs b/config/config.exs index 95e8450b..28c447d3 100644 --- a/config/config.exs +++ b/config/config.exs @@ -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, diff --git a/config/test.exs b/config/test.exs index 06528b16..a35a8174 100644 --- a/config/test.exs +++ b/config/test.exs @@ -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: []