dawarich/spec/policies/family/invitation_policy_spec.rb
2025-10-11 14:17:48 +02:00

264 lines
8.6 KiB
Ruby

# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Family::InvitationPolicy, type: :policy do
let(:family) { create(:family) }
let(:owner) { family.creator }
let(:member) { create(:user) }
let(:other_user) { create(:user) }
let(:invitation) { create(:family_invitation, family: family, invited_by: owner) }
before do
# Set up family membership for owner
create(:family_membership, family: family, user: owner, role: :owner)
# Set up family membership for regular member
create(:family_membership, family: family, user: member, role: :member)
end
describe '#show?' do
context 'with authenticated user' do
it 'allows any authenticated user to view invitation' do
policy = Family::InvitationPolicy.new(owner, invitation)
expect(policy).to permit(:show)
end
it 'allows other users to view invitation' do
policy = Family::InvitationPolicy.new(other_user, invitation)
expect(policy).to permit(:show)
end
end
context 'with unauthenticated user' do
it 'allows unauthenticated access (public endpoint)' do
policy = Family::InvitationPolicy.new(nil, invitation)
expect(policy).to permit(:show)
end
end
end
describe '#create?' do
context 'when user is family owner' do
before do
allow(owner).to receive(:family).and_return(family)
allow(owner).to receive(:family_owner?).and_return(true)
end
it 'allows family owner to create invitations' do
policy = Family::InvitationPolicy.new(owner, invitation)
expect(policy).to permit(:create)
end
end
context 'when user is regular family member' do
before do
allow(member).to receive(:family).and_return(family)
allow(member).to receive(:family_owner?).and_return(false)
end
it 'denies regular family member from creating invitations' do
policy = Family::InvitationPolicy.new(member, invitation)
expect(policy).not_to permit(:create)
end
end
context 'when user is not in the family' do
it 'denies user not in the family from creating invitations' do
policy = Family::InvitationPolicy.new(other_user, invitation)
expect(policy).not_to permit(:create)
end
end
context 'with unauthenticated user' do
it 'denies unauthenticated user from creating invitations' do
policy = Family::InvitationPolicy.new(nil, invitation)
expect(policy).not_to permit(:create)
end
end
end
describe '#accept?' do
context 'when user email matches invitation email' do
let(:invited_user) { create(:user, email: invitation.email) }
it 'allows user to accept invitation sent to their email' do
policy = Family::InvitationPolicy.new(invited_user, invitation)
expect(policy).to permit(:accept)
end
end
context 'when user email does not match invitation email' do
it 'denies user with different email from accepting invitation' do
policy = Family::InvitationPolicy.new(other_user, invitation)
expect(policy).not_to permit(:accept)
end
end
context 'when family owner tries to accept invitation' do
it 'denies family owner from accepting invitation sent to different email' do
policy = Family::InvitationPolicy.new(owner, invitation)
expect(policy).not_to permit(:accept)
end
end
context 'with unauthenticated user' do
it 'denies unauthenticated user from accepting invitation' do
policy = Family::InvitationPolicy.new(nil, invitation)
expect(policy).not_to permit(:accept)
end
end
end
describe '#destroy?' do
context 'when user is family owner' do
before do
allow(owner).to receive(:family).and_return(family)
allow(owner).to receive(:family_owner?).and_return(true)
end
it 'allows family owner to cancel invitations' do
policy = Family::InvitationPolicy.new(owner, invitation)
expect(policy).to permit(:destroy)
end
end
context 'when user is regular family member' do
before do
allow(member).to receive(:family).and_return(family)
allow(member).to receive(:family_owner?).and_return(false)
end
it 'denies regular family member from cancelling invitations' do
policy = Family::InvitationPolicy.new(member, invitation)
expect(policy).not_to permit(:destroy)
end
end
context 'when user is not in the family' do
it 'denies user not in the family from cancelling invitations' do
policy = Family::InvitationPolicy.new(other_user, invitation)
expect(policy).not_to permit(:destroy)
end
end
context 'with unauthenticated user' do
it 'denies unauthenticated user from cancelling invitations' do
policy = Family::InvitationPolicy.new(nil, invitation)
expect(policy).not_to permit(:destroy)
end
end
end
describe 'edge cases' do
context 'when invitation belongs to different family' do
let(:other_family) { create(:family) }
let(:other_family_owner) { other_family.creator }
let(:other_invitation) { create(:family_invitation, family: other_family, invited_by: other_family_owner) }
before do
create(:family_membership, family: other_family, user: other_family_owner, role: :owner)
allow(owner).to receive(:family).and_return(family)
allow(owner).to receive(:family_owner?).and_return(true)
end
it 'denies owner from creating invitations for different family' do
policy = Family::InvitationPolicy.new(owner, other_invitation)
expect(policy).not_to permit(:create)
end
it 'denies owner from destroying invitations for different family' do
policy = Family::InvitationPolicy.new(owner, other_invitation)
expect(policy).not_to permit(:destroy)
end
end
context 'with expired invitation' do
let(:expired_invitation) { create(:family_invitation, :expired, family: family, invited_by: owner) }
let(:invited_user) { create(:user, email: expired_invitation.email) }
it 'still allows user to attempt to accept expired invitation (business logic handles expiry)' do
policy = Family::InvitationPolicy.new(invited_user, expired_invitation)
expect(policy).to permit(:accept)
end
it 'allows owner to destroy expired invitation' do
allow(owner).to receive(:family).and_return(family)
allow(owner).to receive(:family_owner?).and_return(true)
policy = Family::InvitationPolicy.new(owner, expired_invitation)
expect(policy).to permit(:destroy)
end
end
context 'with accepted invitation' do
let(:accepted_invitation) { create(:family_invitation, :accepted, family: family, invited_by: owner) }
it 'allows owner to destroy accepted invitation' do
allow(owner).to receive(:family).and_return(family)
allow(owner).to receive(:family_owner?).and_return(true)
policy = Family::InvitationPolicy.new(owner, accepted_invitation)
expect(policy).to permit(:destroy)
end
end
context 'with cancelled invitation' do
let(:cancelled_invitation) { create(:family_invitation, :cancelled, family: family, invited_by: owner) }
it 'allows owner to destroy cancelled invitation' do
allow(owner).to receive(:family).and_return(family)
allow(owner).to receive(:family_owner?).and_return(true)
policy = Family::InvitationPolicy.new(owner, cancelled_invitation)
expect(policy).to permit(:destroy)
end
end
end
describe 'authorization consistency' do
it 'ensures owner can both create and destroy invitations' do
allow(owner).to receive(:family).and_return(family)
allow(owner).to receive(:family_owner?).and_return(true)
policy = Family::InvitationPolicy.new(owner, invitation)
expect(policy).to permit(:create)
expect(policy).to permit(:destroy)
end
it 'ensures regular members cannot create or destroy invitations' do
allow(member).to receive(:family).and_return(family)
allow(member).to receive(:family_owner?).and_return(false)
policy = Family::InvitationPolicy.new(member, invitation)
expect(policy).not_to permit(:create)
expect(policy).not_to permit(:destroy)
end
it 'ensures invited users can only accept their own invitations' do
invited_user = create(:user, email: invitation.email)
policy = Family::InvitationPolicy.new(invited_user, invitation)
expect(policy).to permit(:accept)
expect(policy).not_to permit(:create)
expect(policy).not_to permit(:destroy)
end
end
end