186 lines
6.2 KiB
Elixir
186 lines
6.2 KiB
Elixir
defmodule Mv.Membership.JoinRequestApprovalDomainTest do
|
||
@moduledoc """
|
||
Domain tests for JoinRequest approval: approve/reject and promotion to Member (Step 2).
|
||
|
||
Asserts that approve creates one Member with mapped data, reject does not create Member,
|
||
status rules, and idempotency. No User creation in MVP.
|
||
"""
|
||
use Mv.DataCase, async: true
|
||
|
||
import Ash.Expr
|
||
require Ash.Query
|
||
|
||
alias Mv.Fixtures
|
||
alias Mv.Helpers.SystemActor
|
||
alias Mv.Membership
|
||
alias Mv.Membership.Member
|
||
|
||
defp member_count do
|
||
actor = SystemActor.get_system_actor()
|
||
{:ok, members} = Membership.list_members(actor: actor)
|
||
length(members)
|
||
end
|
||
|
||
describe "approve_join_request/2 – promotion to Member" do
|
||
test "approve creates exactly one member with email, first_name, last_name from JoinRequest" do
|
||
request =
|
||
Fixtures.submitted_join_request_fixture(%{
|
||
first_name: "Approved",
|
||
last_name: "User"
|
||
})
|
||
|
||
count_before = member_count()
|
||
user = Fixtures.user_with_role_fixture("normal_user")
|
||
|
||
assert {:ok, approved} = Membership.approve_join_request(request.id, actor: user)
|
||
assert approved.status == :approved
|
||
|
||
assert member_count() == count_before + 1
|
||
|
||
request_email = request.email
|
||
|
||
[member] =
|
||
Member
|
||
|> Ash.Query.filter(expr(^ref(:email) == ^request_email))
|
||
|> Ash.read!(actor: SystemActor.get_system_actor(), domain: Membership)
|
||
|
||
assert member.email == request.email
|
||
assert member.first_name == request.first_name
|
||
assert member.last_name == request.last_name
|
||
end
|
||
|
||
test "approve does not create a User (MVP)" do
|
||
request = Fixtures.submitted_join_request_fixture()
|
||
user = Fixtures.user_with_role_fixture("normal_user")
|
||
|
||
assert {:ok, _} = Membership.approve_join_request(request.id, actor: user)
|
||
|
||
# No User should exist with this email from the approval flow
|
||
request_email = request.email
|
||
|
||
users_with_email =
|
||
Mv.Accounts.User
|
||
|> Ash.Query.filter(expr(^ref(:email) == ^request_email))
|
||
|> Ash.read!(authorize?: false)
|
||
|
||
assert users_with_email == []
|
||
end
|
||
end
|
||
|
||
describe "reviewed_by_display" do
|
||
test "get_join_request returns reviewed_by_display so UI can show reviewer without loading User" do
|
||
request = Fixtures.submitted_join_request_fixture()
|
||
reviewer = Fixtures.user_with_role_fixture("normal_user")
|
||
|
||
assert {:ok, _} = Membership.approve_join_request(request.id, actor: reviewer)
|
||
|
||
assert {:ok, loaded} = Membership.get_join_request(request.id, actor: reviewer)
|
||
assert loaded.reviewed_by_display == to_string(reviewer.email)
|
||
end
|
||
end
|
||
|
||
describe "reject_join_request/2" do
|
||
test "reject does not create a member" do
|
||
request = Fixtures.submitted_join_request_fixture()
|
||
count_before = member_count()
|
||
user = Fixtures.user_with_role_fixture("normal_user")
|
||
|
||
assert {:ok, rejected} = Membership.reject_join_request(request.id, actor: user)
|
||
assert rejected.status == :rejected
|
||
assert rejected.rejected_at != nil
|
||
|
||
assert member_count() == count_before
|
||
end
|
||
end
|
||
|
||
describe "approve_join_request/2 – status and idempotency" do
|
||
test "approve when status is already approved is idempotent or returns error" do
|
||
request = Fixtures.submitted_join_request_fixture()
|
||
user = Fixtures.user_with_role_fixture("normal_user")
|
||
|
||
assert {:ok, _} = Membership.approve_join_request(request.id, actor: user)
|
||
count_after_first = member_count()
|
||
|
||
# Second approve: either {:ok, request} with no duplicate member, or {:error, _}
|
||
result = Membership.approve_join_request(request.id, actor: user)
|
||
|
||
if match?({:ok, _}, result) do
|
||
assert member_count() == count_after_first
|
||
else
|
||
assert {:error, _} = result
|
||
end
|
||
end
|
||
|
||
test "approve when status is pending_confirmation returns error" do
|
||
token = "pending-token-#{System.unique_integer([:positive])}"
|
||
|
||
attrs = %{
|
||
email: "pending#{System.unique_integer([:positive])}@example.com",
|
||
confirmation_token: token
|
||
}
|
||
|
||
{:ok, request} = Membership.submit_join_request(attrs, actor: nil)
|
||
assert request.status == :pending_confirmation
|
||
|
||
user = Fixtures.user_with_role_fixture("normal_user")
|
||
assert {:error, _} = Membership.approve_join_request(request.id, actor: user)
|
||
end
|
||
|
||
test "approve when status is rejected returns error" do
|
||
request = Fixtures.submitted_join_request_fixture()
|
||
user = Fixtures.user_with_role_fixture("normal_user")
|
||
assert {:ok, _} = Membership.reject_join_request(request.id, actor: user)
|
||
|
||
assert {:error, _} = Membership.approve_join_request(request.id, actor: user)
|
||
end
|
||
end
|
||
|
||
describe "approve_join_request/2 – defaults" do
|
||
setup do
|
||
# Create a fee type and set it as the default in settings so SetDefaultMembershipFeeType
|
||
# can assign it when a member is created from a join request (no fee type in form_data).
|
||
actor = SystemActor.get_system_actor()
|
||
|
||
{:ok, fee_type} =
|
||
Ash.create(
|
||
Mv.MembershipFees.MembershipFeeType,
|
||
%{
|
||
name: "Default Test Fee Type #{System.unique_integer([:positive])}",
|
||
amount: Decimal.new("50.00"),
|
||
interval: :yearly
|
||
},
|
||
actor: actor,
|
||
domain: Mv.MembershipFees
|
||
)
|
||
|
||
{:ok, settings} = Membership.get_settings()
|
||
|
||
settings
|
||
|> Ash.Changeset.for_update(
|
||
:update_membership_fee_settings,
|
||
%{default_membership_fee_type_id: fee_type.id},
|
||
actor: actor
|
||
)
|
||
|> Ash.update!(actor: actor)
|
||
|
||
:ok
|
||
end
|
||
|
||
test "created member has join_date and membership_fee_type when not in form_data" do
|
||
request = Fixtures.submitted_join_request_fixture()
|
||
user = Fixtures.user_with_role_fixture("normal_user")
|
||
|
||
assert {:ok, _} = Membership.approve_join_request(request.id, actor: user)
|
||
|
||
request_email = request.email
|
||
|
||
[member] =
|
||
Member
|
||
|> Ash.Query.filter(expr(^ref(:email) == ^request_email))
|
||
|> Ash.read!(actor: SystemActor.get_system_actor(), domain: Membership)
|
||
|
||
assert member.join_date != nil
|
||
assert member.membership_fee_type_id != nil
|
||
end
|
||
end
|
||
end
|