defmodule PlausibleWeb.Site.InvitationControllerTest do use PlausibleWeb.ConnCase, async: true use Plausible.Repo use Bamboo.Test setup [:create_user, :log_in] describe "POST /sites/invitations/:invitation_id/accept" do test "converts the invitation into a membership", %{conn: conn, user: user} do site = insert(:site) invitation = insert(:invitation, site_id: site.id, inviter: build(:user), email: user.email, role: :admin ) conn = post(conn, "/sites/invitations/#{invitation.invitation_id}/accept") assert Phoenix.Flash.get(conn.assigns.flash, :success) == "You now have access to #{site.domain}" assert redirected_to(conn) == "/#{URI.encode_www_form(site.domain)}" refute Repo.exists?(from(i in Plausible.Auth.Invitation, where: i.email == ^user.email)) membership = Repo.get_by(Plausible.Site.Membership, user_id: user.id, site_id: site.id) assert membership.role == :admin end test "does not crash if clicked for the 2nd time in another tab", %{conn: conn, user: user} do site = insert(:site) invitation = insert(:invitation, site_id: site.id, inviter: build(:user), email: user.email, role: :admin ) c1 = post(conn, "/sites/invitations/#{invitation.invitation_id}/accept") assert redirected_to(c1) == "/#{URI.encode_www_form(site.domain)}" assert Phoenix.Flash.get(c1.assigns.flash, :success) == "You now have access to #{site.domain}" c2 = post(conn, "/sites/invitations/#{invitation.invitation_id}/accept") assert redirected_to(c2) == "/sites" assert Phoenix.Flash.get(c2.assigns.flash, :error) == "Invitation missing or already accepted" end end describe "POST /sites/invitations/:invitation_id/accept - ownership transfer" do test "downgrades previous owner to admin", %{conn: conn, user: user} do old_owner = insert(:user) site = insert(:site, members: [old_owner]) insert(:growth_subscription, user: user) invitation = insert(:invitation, site_id: site.id, inviter: old_owner, email: user.email, role: :owner) conn = post(conn, "/sites/invitations/#{invitation.invitation_id}/accept") assert redirected_to(conn, 302) == "/#{URI.encode_www_form(site.domain)}" assert Phoenix.Flash.get(conn.assigns.flash, :success) =~ "You now have access to" refute Repo.exists?(from(i in Plausible.Auth.Invitation, where: i.email == ^user.email)) old_owner_membership = Repo.get_by(Plausible.Site.Membership, user_id: old_owner.id, site_id: site.id) assert old_owner_membership.role == :admin new_owner_membership = Repo.get_by(Plausible.Site.Membership, user_id: user.id, site_id: site.id) assert new_owner_membership.role == :owner end @tag :full_build_only test "fails when new owner has no plan", %{conn: conn, user: user} do old_owner = insert(:user) site = insert(:site, members: [old_owner]) invitation = insert(:invitation, site_id: site.id, inviter: old_owner, email: user.email, role: :owner) conn = post(conn, "/sites/invitations/#{invitation.invitation_id}/accept") assert redirected_to(conn, 302) == Routes.site_path(conn, :index) assert Phoenix.Flash.get(conn.assigns.flash, :error) =~ "No existing subscription" end @tag :full_build_only test "fails when new owner's plan is unsuitable", %{conn: conn, user: user} do old_owner = insert(:user) site = insert(:site, members: [old_owner]) insert(:growth_subscription, user: user) # fill site limit quota insert_list(10, :site, members: [user]) invitation = insert(:invitation, site_id: site.id, inviter: old_owner, email: user.email, role: :owner) conn = post(conn, "/sites/invitations/#{invitation.invitation_id}/accept") assert redirected_to(conn, 302) == Routes.site_path(conn, :index) assert Phoenix.Flash.get(conn.assigns.flash, :error) =~ "Plan limits exceeded: site limit." end end describe "POST /sites/invitations/:invitation_id/reject" do test "rejects the invitation", %{conn: conn, user: user} do site = insert(:site) invitation = insert(:invitation, site_id: site.id, inviter: build(:user), email: user.email, role: :admin ) conn = post(conn, "/sites/invitations/#{invitation.invitation_id}/reject") assert redirected_to(conn, 302) == "/sites" refute Repo.reload(invitation) end test "renders error for non-existent invitation", %{conn: conn} do conn = post(conn, "/sites/invitations/does-not-exist/reject") assert redirected_to(conn, 302) == "/sites" assert Phoenix.Flash.get(conn.assigns.flash, :error) == "Invitation missing or already accepted" end end describe "DELETE /sites/:website/invitations/:invitation_id" do test "removes the invitation", %{conn: conn, user: user} do site = insert(:site, memberships: [build(:site_membership, user: user, role: :admin)]) invitation = insert(:invitation, site_id: site.id, inviter: build(:user), email: "jane@example.com", role: :admin ) conn = delete( conn, Routes.invitation_path(conn, :remove_invitation, site.domain, invitation.invitation_id) ) assert redirected_to(conn, 302) == "/#{URI.encode_www_form(site.domain)}/settings/people" refute Repo.reload(invitation) end test "fails to remove an invitation with insufficient permission", %{conn: conn, user: user} do site = insert(:site, memberships: [build(:site_membership, user: user, role: :viewer)]) invitation = insert(:invitation, site_id: site.id, inviter: build(:user), email: "jane@example.com", role: :admin ) delete( conn, Routes.invitation_path(conn, :remove_invitation, site.domain, invitation.invitation_id) ) assert Repo.reload(invitation) end test "fails to remove an invitation from the outside", %{conn: my_conn, user: me} do _my_site = insert(:site, memberships: [build(:site_membership, user: me, role: "owner")]) other_user = insert(:user) other_site = insert(:site, memberships: [build(:site_membership, user: other_user, role: "owner")]) invitation = insert(:invitation, site_id: other_site.id, inviter: other_user, email: "jane@example.com", role: :admin ) remove_invitation_path = Routes.invitation_path( my_conn, :remove_invitation, other_site.domain, invitation.invitation_id ) delete(my_conn, remove_invitation_path) assert Repo.reload(invitation) end test "renders error for non-existent invitation", %{conn: conn, user: user} do site = insert(:site, memberships: [build(:site_membership, user: user, role: :admin)]) remove_invitation_path = Routes.invitation_path( conn, :remove_invitation, site.domain, "does_not_exist" ) conn = delete(conn, remove_invitation_path) assert redirected_to(conn, 302) == "/#{URI.encode_www_form(site.domain)}/settings/people" assert Phoenix.Flash.get(conn.assigns.flash, :error) == "Invitation missing or already removed" end end end