# frozen_string_literal: true require 'rails_helper' RSpec.describe Families::Invite do let(:owner) { create(:user) } let(:family) { create(:family, creator: owner) } let!(:owner_membership) { create(:family_membership, user: owner, family: family, role: :owner) } let(:email) { 'invitee@example.com' } let(:service) { described_class.new(family: family, email: email, invited_by: owner) } describe '#call' do context 'when invitation is valid' do it 'creates an invitation' do expect { service.call }.to change(Family::Invitation, :count).by(1) invitation = owner.sent_family_invitations.last expect(invitation.family).to eq(family) expect(invitation.email).to eq(email) expect(invitation.invited_by).to eq(owner) end it 'sends invitation email' do expect(FamilyMailer).to receive(:invitation).and_call_original expect_any_instance_of(ActionMailer::MessageDelivery).to receive(:deliver_later) service.call end it 'sends notification to inviter' do expect { service.call }.to change(Notification, :count).by(1) notification = owner.notifications.last expect(notification.user).to eq(owner) expect(notification.title).to eq('Invitation Sent') end it 'returns true' do expect(service.call).to be true end end context 'when inviter is not family owner' do let(:member) { create(:user) } let!(:member_membership) { create(:family_membership, user: member, family: family, role: :member) } let(:service) { described_class.new(family: family, email: email, invited_by: member) } it 'returns false' do expect(service.call).to be false end it 'does not create invitation' do expect { service.call }.not_to change(Family::Invitation, :count) end end context 'when family is at max capacity' do before do # Create max members (5 total including owner) create_list(:family_membership, Family::MAX_MEMBERS - 1, family: family, role: :member) end it 'returns false' do expect(service.call).to be false end it 'does not create invitation' do expect { service.call }.not_to change(Family::Invitation, :count) end end context 'when user is already in a family' do let(:existing_user) { create(:user, email: email) } let(:other_family) { create(:family) } before do create(:family_membership, user: existing_user, family: other_family) end it 'returns false' do expect(service.call).to be false end it 'does not create invitation' do expect { service.call }.not_to change(Family::Invitation, :count) end end context 'when pending invitation already exists' do before do create(:family_invitation, family: family, email: email, invited_by: owner) end it 'returns false' do expect(service.call).to be false end it 'does not create another invitation' do expect { service.call }.not_to change(Family::Invitation, :count) end end context 'with invalid email' do let(:service) { described_class.new(family: family, email: 'invalid-email', invited_by: owner) } it 'returns false' do expect(service.call).to be false end it 'has validation errors' do service.call expect(service.errors[:email]).to be_present end end end describe 'email normalization' do let(:service) { described_class.new(family: family, email: ' UPPER@EXAMPLE.COM ', invited_by: owner) } it 'normalizes email to lowercase and strips whitespace' do service.call invitation = family.family_invitations.last expect(invitation.email).to eq('upper@example.com') end end describe 'validations' do it 'validates presence of email' do service = described_class.new(family: family, email: '', invited_by: owner) expect(service).not_to be_valid expect(service.errors[:email]).to include("can't be blank") end it 'validates email format' do service = described_class.new(family: family, email: 'invalid-email', invited_by: owner) expect(service).not_to be_valid expect(service.errors[:email]).to include('is invalid') end end end