- phantom_players table for tracking scores of non-user members - claim flow: officer assigns phantom to user, user confirms, scores transfer - CRUD endpoints plus /assign and /confirm_claim actions - model/request specs for all functionality (37 examples) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
162 lines
5.6 KiB
Ruby
162 lines
5.6 KiB
Ruby
require 'rails_helper'
|
|
|
|
RSpec.describe PhantomPlayer, type: :model do
|
|
describe 'associations' do
|
|
it { should belong_to(:crew) }
|
|
it { should belong_to(:claimed_by).class_name('User').optional }
|
|
it { should belong_to(:claimed_from_membership).class_name('CrewMembership').optional }
|
|
it { should have_many(:gw_individual_scores) }
|
|
end
|
|
|
|
describe 'validations' do
|
|
it { should validate_presence_of(:name) }
|
|
it { should validate_length_of(:name).is_at_most(100) }
|
|
it { should validate_length_of(:granblue_id).is_at_most(20) }
|
|
|
|
describe 'granblue_id uniqueness within crew' do
|
|
let(:crew) { create(:crew) }
|
|
let!(:existing) { create(:phantom_player, crew: crew, granblue_id: '12345678') }
|
|
|
|
it 'rejects duplicate granblue_id in same crew' do
|
|
phantom = build(:phantom_player, crew: crew, granblue_id: '12345678')
|
|
expect(phantom).not_to be_valid
|
|
expect(phantom.errors[:granblue_id]).to be_present
|
|
end
|
|
|
|
it 'allows same granblue_id in different crew' do
|
|
other_crew = create(:crew)
|
|
phantom = build(:phantom_player, crew: other_crew, granblue_id: '12345678')
|
|
expect(phantom).to be_valid
|
|
end
|
|
|
|
it 'allows multiple nil granblue_ids in same crew' do
|
|
phantom = build(:phantom_player, crew: crew, granblue_id: nil)
|
|
expect(phantom).to be_valid
|
|
end
|
|
end
|
|
|
|
describe 'claimed_by must be crew member' do
|
|
let(:crew) { create(:crew) }
|
|
let(:phantom) { build(:phantom_player, crew: crew) }
|
|
|
|
it 'accepts claimed_by who is a crew member' do
|
|
member = create(:user)
|
|
create(:crew_membership, crew: crew, user: member, role: :member)
|
|
phantom.claimed_by = member
|
|
expect(phantom).to be_valid
|
|
end
|
|
|
|
it 'rejects claimed_by who is not a crew member' do
|
|
non_member = create(:user)
|
|
phantom.claimed_by = non_member
|
|
expect(phantom).not_to be_valid
|
|
expect(phantom.errors[:claimed_by]).to include('must be a member of this crew')
|
|
end
|
|
end
|
|
|
|
describe 'claim_confirmed requires claimed_by' do
|
|
let(:phantom) { build(:phantom_player, claim_confirmed: true, claimed_by: nil) }
|
|
|
|
it 'is invalid' do
|
|
expect(phantom).not_to be_valid
|
|
expect(phantom.errors[:claim_confirmed]).to include('requires a claimed_by user')
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'scopes' do
|
|
let(:crew) { create(:crew) }
|
|
let(:member) { create(:user) }
|
|
let!(:membership) { create(:crew_membership, crew: crew, user: member, role: :member) }
|
|
let!(:unclaimed) { create(:phantom_player, crew: crew) }
|
|
let!(:claimed) { create(:phantom_player, crew: crew, claimed_by: member) }
|
|
let!(:confirmed) { create(:phantom_player, crew: crew, claimed_by: member, claim_confirmed: true) }
|
|
|
|
describe '.unclaimed' do
|
|
it 'returns only unclaimed phantoms' do
|
|
expect(PhantomPlayer.unclaimed).to contain_exactly(unclaimed)
|
|
end
|
|
end
|
|
|
|
describe '.claimed' do
|
|
it 'returns claimed phantoms (confirmed or not)' do
|
|
expect(PhantomPlayer.claimed).to contain_exactly(claimed, confirmed)
|
|
end
|
|
end
|
|
|
|
describe '.pending_confirmation' do
|
|
it 'returns claimed but unconfirmed phantoms' do
|
|
expect(PhantomPlayer.pending_confirmation).to contain_exactly(claimed)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#assign_to' do
|
|
let(:crew) { create(:crew) }
|
|
let(:phantom) { create(:phantom_player, crew: crew) }
|
|
let(:member) { create(:user) }
|
|
let!(:membership) { create(:crew_membership, crew: crew, user: member, role: :member) }
|
|
|
|
it 'assigns the phantom to the user' do
|
|
phantom.assign_to(member)
|
|
expect(phantom.claimed_by).to eq(member)
|
|
expect(phantom.claim_confirmed).to be false
|
|
end
|
|
|
|
it 'raises error for non-crew member' do
|
|
non_member = create(:user)
|
|
expect { phantom.assign_to(non_member) }.to raise_error(CrewErrors::MemberNotFoundError)
|
|
end
|
|
end
|
|
|
|
describe '#confirm_claim!' do
|
|
let(:crew) { create(:crew) }
|
|
let(:member) { create(:user) }
|
|
let!(:membership) { create(:crew_membership, crew: crew, user: member, role: :member) }
|
|
let(:phantom) { create(:phantom_player, crew: crew, claimed_by: member) }
|
|
|
|
it 'confirms the claim' do
|
|
phantom.confirm_claim!(member)
|
|
expect(phantom.claim_confirmed).to be true
|
|
end
|
|
|
|
it 'raises error for wrong user' do
|
|
other_user = create(:user)
|
|
create(:crew_membership, crew: crew, user: other_user, role: :member)
|
|
expect { phantom.confirm_claim!(other_user) }.to raise_error(CrewErrors::NotClaimedByUserError)
|
|
end
|
|
|
|
context 'with individual scores' do
|
|
let(:gw_event) { create(:gw_event) }
|
|
let(:participation) { create(:crew_gw_participation, crew: crew, gw_event: gw_event) }
|
|
let!(:phantom_score) do
|
|
create(:gw_individual_score,
|
|
crew_gw_participation: participation,
|
|
phantom_player: phantom,
|
|
crew_membership: nil,
|
|
score: 1_000_000)
|
|
end
|
|
|
|
it 'transfers scores to the membership' do
|
|
phantom.confirm_claim!(member)
|
|
phantom_score.reload
|
|
|
|
expect(phantom_score.crew_membership).to eq(membership)
|
|
expect(phantom_score.phantom_player).to be_nil
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#unassign!' do
|
|
let(:crew) { create(:crew) }
|
|
let(:member) { create(:user) }
|
|
let!(:membership) { create(:crew_membership, crew: crew, user: member, role: :member) }
|
|
let(:phantom) { create(:phantom_player, crew: crew, claimed_by: member) }
|
|
|
|
it 'removes the assignment' do
|
|
phantom.unassign!
|
|
expect(phantom.claimed_by).to be_nil
|
|
expect(phantom.claim_confirmed).to be false
|
|
end
|
|
end
|
|
end
|