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