add request specs for crew controllers (phases 1-2)
- add crews_spec.rb (18 examples) - add crew_memberships_spec.rb (13 examples) - add crew_invitations_spec.rb (15 examples) - fix crew_memberships authorize_crew_captain! as before_action - update crew_invitations factory to auto-set invited_by officer 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
7f57c2c3ee
commit
a3a0138526
5 changed files with 608 additions and 5 deletions
|
|
@ -9,13 +9,10 @@ module Api
|
|||
before_action :set_crew
|
||||
before_action :set_membership, only: %i[update destroy promote demote]
|
||||
before_action :authorize_crew_officer!, only: %i[destroy]
|
||||
before_action :authorize_crew_captain!, only: %i[promote demote]
|
||||
before_action :authorize_crew_captain!, only: %i[update promote demote]
|
||||
|
||||
# PUT /crews/:crew_id/memberships/:id
|
||||
def update
|
||||
# Only captain can update roles
|
||||
authorize_crew_captain!
|
||||
|
||||
if @membership.update(membership_params)
|
||||
render json: CrewMembershipBlueprint.render(@membership, view: :with_user, root: :membership)
|
||||
else
|
||||
|
|
|
|||
|
|
@ -2,10 +2,22 @@ FactoryBot.define do
|
|||
factory :crew_invitation do
|
||||
crew
|
||||
user
|
||||
association :invited_by, factory: :user
|
||||
status { :pending }
|
||||
expires_at { 7.days.from_now }
|
||||
|
||||
# invited_by must be an officer of the crew
|
||||
after(:build) do |invitation, _evaluator|
|
||||
unless invitation.invited_by
|
||||
# Create an officer for the crew if one doesn't exist
|
||||
officer = invitation.crew.crew_memberships.find_by(role: [:captain, :vice_captain], retired: false)&.user
|
||||
unless officer
|
||||
officer = create(:user)
|
||||
create(:crew_membership, crew: invitation.crew, user: officer, role: :captain)
|
||||
end
|
||||
invitation.invited_by = officer
|
||||
end
|
||||
end
|
||||
|
||||
trait :accepted do
|
||||
status { :accepted }
|
||||
end
|
||||
|
|
|
|||
203
spec/requests/api/v1/crew_invitations_spec.rb
Normal file
203
spec/requests/api/v1/crew_invitations_spec.rb
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Api::V1::CrewInvitations', type: :request do
|
||||
let(:user) { create(:user) }
|
||||
let(:access_token) do
|
||||
Doorkeeper::AccessToken.create!(resource_owner_id: user.id, expires_in: 30.days, scopes: 'public')
|
||||
end
|
||||
let(:auth_headers) { { 'Authorization' => "Bearer #{access_token.token}" } }
|
||||
|
||||
let(:crew) { create(:crew) }
|
||||
let!(:captain_membership) { create(:crew_membership, crew: crew, user: user, role: :captain) }
|
||||
|
||||
describe 'GET /api/v1/crews/:crew_id/invitations' do
|
||||
context 'as officer' do
|
||||
let!(:pending_invitation) { create(:crew_invitation, crew: crew, invited_by: user, status: :pending) }
|
||||
let!(:accepted_invitation) { create(:crew_invitation, crew: crew, invited_by: user, status: :accepted) }
|
||||
|
||||
it 'returns pending invitations' do
|
||||
get "/api/v1/crews/#{crew.id}/invitations", headers: auth_headers
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
json = JSON.parse(response.body)
|
||||
expect(json['invitations'].length).to eq(1)
|
||||
expect(json['invitations'][0]['id']).to eq(pending_invitation.id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'as regular member' do
|
||||
let(:actual_captain) { create(:user) }
|
||||
let!(:captain_membership) { create(:crew_membership, crew: crew, user: actual_captain, role: :captain) }
|
||||
let!(:member_membership) { create(:crew_membership, crew: crew, user: user, role: :member) }
|
||||
|
||||
it 'returns unauthorized' do
|
||||
get "/api/v1/crews/#{crew.id}/invitations", headers: auth_headers
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /api/v1/crews/:crew_id/invitations' do
|
||||
let(:invitee) { create(:user) }
|
||||
|
||||
context 'as officer' do
|
||||
it 'creates an invitation by user_id' do
|
||||
expect {
|
||||
post "/api/v1/crews/#{crew.id}/invitations",
|
||||
params: { user_id: invitee.id },
|
||||
headers: auth_headers
|
||||
}.to change(CrewInvitation, :count).by(1)
|
||||
|
||||
expect(response).to have_http_status(:created)
|
||||
json = JSON.parse(response.body)
|
||||
expect(json['invitation']['user']['id']).to eq(invitee.id)
|
||||
end
|
||||
|
||||
it 'creates an invitation by username' do
|
||||
post "/api/v1/crews/#{crew.id}/invitations",
|
||||
params: { username: invitee.username },
|
||||
headers: auth_headers
|
||||
|
||||
expect(response).to have_http_status(:created)
|
||||
end
|
||||
|
||||
it 'returns error for non-existent user' do
|
||||
post "/api/v1/crews/#{crew.id}/invitations",
|
||||
params: { user_id: SecureRandom.uuid },
|
||||
headers: auth_headers
|
||||
|
||||
expect(response).to have_http_status(:not_found)
|
||||
end
|
||||
|
||||
it 'returns error for self-invitation' do
|
||||
post "/api/v1/crews/#{crew.id}/invitations",
|
||||
params: { user_id: user.id },
|
||||
headers: auth_headers
|
||||
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
json = JSON.parse(response.body)
|
||||
expect(json['code']).to eq('cannot_invite_self')
|
||||
end
|
||||
|
||||
it 'returns error if user already in a crew' do
|
||||
other_crew = create(:crew)
|
||||
create(:crew_membership, crew: other_crew, user: invitee)
|
||||
|
||||
post "/api/v1/crews/#{crew.id}/invitations",
|
||||
params: { user_id: invitee.id },
|
||||
headers: auth_headers
|
||||
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
json = JSON.parse(response.body)
|
||||
expect(json['code']).to eq('already_in_crew')
|
||||
end
|
||||
|
||||
it 'returns error if user already invited' do
|
||||
create(:crew_invitation, crew: crew, user: invitee, invited_by: user, status: :pending)
|
||||
|
||||
post "/api/v1/crews/#{crew.id}/invitations",
|
||||
params: { user_id: invitee.id },
|
||||
headers: auth_headers
|
||||
|
||||
expect(response).to have_http_status(:conflict)
|
||||
json = JSON.parse(response.body)
|
||||
expect(json['code']).to eq('user_already_invited')
|
||||
end
|
||||
end
|
||||
|
||||
context 'as vice captain' do
|
||||
let!(:captain_membership) { create(:crew_membership, crew: crew, user: user, role: :vice_captain) }
|
||||
|
||||
it 'can create invitations' do
|
||||
post "/api/v1/crews/#{crew.id}/invitations",
|
||||
params: { user_id: invitee.id },
|
||||
headers: auth_headers
|
||||
|
||||
expect(response).to have_http_status(:created)
|
||||
end
|
||||
end
|
||||
|
||||
context 'as regular member' do
|
||||
let!(:captain_membership) { create(:crew_membership, crew: crew, user: user, role: :member) }
|
||||
|
||||
it 'returns unauthorized' do
|
||||
post "/api/v1/crews/#{crew.id}/invitations",
|
||||
params: { user_id: invitee.id },
|
||||
headers: auth_headers
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v1/invitations/pending' do
|
||||
let(:invitee) { create(:user) }
|
||||
let(:invitee_token) do
|
||||
Doorkeeper::AccessToken.create!(resource_owner_id: invitee.id, expires_in: 30.days, scopes: 'public')
|
||||
end
|
||||
let(:invitee_headers) { { 'Authorization' => "Bearer #{invitee_token.token}" } }
|
||||
|
||||
let!(:pending1) { create(:crew_invitation, user: invitee, status: :pending) }
|
||||
let!(:pending2) { create(:crew_invitation, user: invitee, status: :pending) }
|
||||
let!(:expired) { create(:crew_invitation, user: invitee, status: :expired) }
|
||||
|
||||
it 'returns pending invitations for current user' do
|
||||
get '/api/v1/invitations/pending', headers: invitee_headers
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
json = JSON.parse(response.body)
|
||||
expect(json['invitations'].length).to eq(2)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /api/v1/invitations/:id/accept' do
|
||||
let(:invitee) { create(:user) }
|
||||
let(:invitee_token) do
|
||||
Doorkeeper::AccessToken.create!(resource_owner_id: invitee.id, expires_in: 30.days, scopes: 'public')
|
||||
end
|
||||
let(:invitee_headers) { { 'Authorization' => "Bearer #{invitee_token.token}" } }
|
||||
let!(:invitation) { create(:crew_invitation, crew: crew, user: invitee, status: :pending) }
|
||||
|
||||
it 'accepts the invitation and joins the crew' do
|
||||
post "/api/v1/invitations/#{invitation.id}/accept", headers: invitee_headers
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
json = JSON.parse(response.body)
|
||||
expect(json['crew']['id']).to eq(crew.id)
|
||||
expect(invitation.reload.status).to eq('accepted')
|
||||
expect(invitee.reload.crew).to eq(crew)
|
||||
end
|
||||
|
||||
it 'returns error for wrong user' do
|
||||
post "/api/v1/invitations/#{invitation.id}/accept", headers: auth_headers
|
||||
|
||||
expect(response).to have_http_status(:not_found)
|
||||
json = JSON.parse(response.body)
|
||||
expect(json['code']).to eq('invitation_not_found')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /api/v1/invitations/:id/reject' do
|
||||
let(:invitee) { create(:user) }
|
||||
let(:invitee_token) do
|
||||
Doorkeeper::AccessToken.create!(resource_owner_id: invitee.id, expires_in: 30.days, scopes: 'public')
|
||||
end
|
||||
let(:invitee_headers) { { 'Authorization' => "Bearer #{invitee_token.token}" } }
|
||||
let!(:invitation) { create(:crew_invitation, crew: crew, user: invitee, status: :pending) }
|
||||
|
||||
it 'rejects the invitation' do
|
||||
post "/api/v1/invitations/#{invitation.id}/reject", headers: invitee_headers
|
||||
|
||||
expect(response).to have_http_status(:no_content)
|
||||
expect(invitation.reload.status).to eq('rejected')
|
||||
end
|
||||
|
||||
it 'returns error for wrong user' do
|
||||
post "/api/v1/invitations/#{invitation.id}/reject", headers: auth_headers
|
||||
|
||||
expect(response).to have_http_status(:not_found)
|
||||
json = JSON.parse(response.body)
|
||||
expect(json['code']).to eq('invitation_not_found')
|
||||
end
|
||||
end
|
||||
end
|
||||
167
spec/requests/api/v1/crew_memberships_spec.rb
Normal file
167
spec/requests/api/v1/crew_memberships_spec.rb
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Api::V1::CrewMemberships', type: :request do
|
||||
let(:user) { create(:user) }
|
||||
let(:access_token) do
|
||||
Doorkeeper::AccessToken.create!(resource_owner_id: user.id, expires_in: 30.days, scopes: 'public')
|
||||
end
|
||||
let(:auth_headers) { { 'Authorization' => "Bearer #{access_token.token}" } }
|
||||
|
||||
let(:crew) { create(:crew) }
|
||||
let(:target_user) { create(:user) }
|
||||
let!(:target_membership) { create(:crew_membership, crew: crew, user: target_user, role: :member) }
|
||||
|
||||
describe 'PUT /api/v1/crews/:crew_id/memberships/:id' do
|
||||
context 'as captain' do
|
||||
let!(:captain_membership) { create(:crew_membership, crew: crew, user: user, role: :captain) }
|
||||
|
||||
it 'updates member role' do
|
||||
put "/api/v1/crews/#{crew.id}/memberships/#{target_membership.id}",
|
||||
params: { membership: { role: 'vice_captain' } },
|
||||
headers: auth_headers
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
json = JSON.parse(response.body)
|
||||
expect(json['membership']['role']).to eq('vice_captain')
|
||||
end
|
||||
end
|
||||
|
||||
context 'as vice captain' do
|
||||
let!(:vc_membership) { create(:crew_membership, crew: crew, user: user, role: :vice_captain) }
|
||||
|
||||
it 'returns unauthorized' do
|
||||
put "/api/v1/crews/#{crew.id}/memberships/#{target_membership.id}",
|
||||
params: { membership: { role: 'vice_captain' } },
|
||||
headers: auth_headers
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'DELETE /api/v1/crews/:crew_id/memberships/:id' do
|
||||
context 'as captain' do
|
||||
let!(:captain_membership) { create(:crew_membership, crew: crew, user: user, role: :captain) }
|
||||
|
||||
it 'removes a member' do
|
||||
delete "/api/v1/crews/#{crew.id}/memberships/#{target_membership.id}",
|
||||
headers: auth_headers
|
||||
|
||||
expect(response).to have_http_status(:no_content)
|
||||
expect(target_membership.reload.retired).to be true
|
||||
end
|
||||
|
||||
it 'cannot remove self (captain)' do
|
||||
delete "/api/v1/crews/#{crew.id}/memberships/#{captain_membership.id}",
|
||||
headers: auth_headers
|
||||
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
json = JSON.parse(response.body)
|
||||
expect(json['code']).to eq('cannot_remove_captain')
|
||||
end
|
||||
end
|
||||
|
||||
context 'as vice captain' do
|
||||
let!(:vc_membership) { create(:crew_membership, crew: crew, user: user, role: :vice_captain) }
|
||||
|
||||
it 'can remove regular members' do
|
||||
delete "/api/v1/crews/#{crew.id}/memberships/#{target_membership.id}",
|
||||
headers: auth_headers
|
||||
|
||||
expect(response).to have_http_status(:no_content)
|
||||
end
|
||||
end
|
||||
|
||||
context 'as regular member' do
|
||||
let!(:membership) { create(:crew_membership, crew: crew, user: user, role: :member) }
|
||||
|
||||
it 'returns unauthorized' do
|
||||
delete "/api/v1/crews/#{crew.id}/memberships/#{target_membership.id}",
|
||||
headers: auth_headers
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /api/v1/crews/:crew_id/memberships/:id/promote' do
|
||||
context 'as captain' do
|
||||
let!(:captain_membership) { create(:crew_membership, crew: crew, user: user, role: :captain) }
|
||||
|
||||
it 'promotes member to vice captain' do
|
||||
post "/api/v1/crews/#{crew.id}/memberships/#{target_membership.id}/promote",
|
||||
headers: auth_headers
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
json = JSON.parse(response.body)
|
||||
expect(json['membership']['role']).to eq('vice_captain')
|
||||
end
|
||||
|
||||
it 'returns error when VC limit reached' do
|
||||
3.times { create(:crew_membership, crew: crew, role: :vice_captain) }
|
||||
|
||||
post "/api/v1/crews/#{crew.id}/memberships/#{target_membership.id}/promote",
|
||||
headers: auth_headers
|
||||
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
json = JSON.parse(response.body)
|
||||
expect(json['code']).to eq('vice_captain_limit')
|
||||
end
|
||||
|
||||
it 'cannot promote captain' do
|
||||
post "/api/v1/crews/#{crew.id}/memberships/#{captain_membership.id}/promote",
|
||||
headers: auth_headers
|
||||
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
end
|
||||
end
|
||||
|
||||
context 'as vice captain' do
|
||||
let!(:vc_membership) { create(:crew_membership, crew: crew, user: user, role: :vice_captain) }
|
||||
|
||||
it 'returns unauthorized' do
|
||||
post "/api/v1/crews/#{crew.id}/memberships/#{target_membership.id}/promote",
|
||||
headers: auth_headers
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /api/v1/crews/:crew_id/memberships/:id/demote' do
|
||||
let!(:vc_target) { create(:crew_membership, crew: crew, role: :vice_captain) }
|
||||
|
||||
context 'as captain' do
|
||||
let!(:captain_membership) { create(:crew_membership, crew: crew, user: user, role: :captain) }
|
||||
|
||||
it 'demotes vice captain to member' do
|
||||
post "/api/v1/crews/#{crew.id}/memberships/#{vc_target.id}/demote",
|
||||
headers: auth_headers
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
json = JSON.parse(response.body)
|
||||
expect(json['membership']['role']).to eq('member')
|
||||
end
|
||||
|
||||
it 'cannot demote captain' do
|
||||
post "/api/v1/crews/#{crew.id}/memberships/#{captain_membership.id}/demote",
|
||||
headers: auth_headers
|
||||
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
json = JSON.parse(response.body)
|
||||
expect(json['code']).to eq('cannot_demote_captain')
|
||||
end
|
||||
end
|
||||
|
||||
context 'as vice captain' do
|
||||
let!(:vc_membership) { create(:crew_membership, crew: crew, user: user, role: :vice_captain) }
|
||||
|
||||
it 'returns unauthorized' do
|
||||
post "/api/v1/crews/#{crew.id}/memberships/#{vc_target.id}/demote",
|
||||
headers: auth_headers
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
224
spec/requests/api/v1/crews_spec.rb
Normal file
224
spec/requests/api/v1/crews_spec.rb
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Api::V1::Crews', type: :request do
|
||||
let(:user) { create(:user) }
|
||||
let(:access_token) do
|
||||
Doorkeeper::AccessToken.create!(resource_owner_id: user.id, expires_in: 30.days, scopes: 'public')
|
||||
end
|
||||
let(:auth_headers) { { 'Authorization' => "Bearer #{access_token.token}" } }
|
||||
|
||||
describe 'POST /api/v1/crews' do
|
||||
let(:valid_params) do
|
||||
{
|
||||
crew: {
|
||||
name: 'Test Crew',
|
||||
gamertag: 'TEST',
|
||||
description: 'A test crew'
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
context 'when user has no crew' do
|
||||
it 'creates a crew and makes user captain' do
|
||||
expect {
|
||||
post '/api/v1/crews', params: valid_params, headers: auth_headers
|
||||
}.to change(Crew, :count).by(1)
|
||||
.and change(CrewMembership, :count).by(1)
|
||||
|
||||
expect(response).to have_http_status(:created)
|
||||
json = JSON.parse(response.body)
|
||||
expect(json['crew']['name']).to eq('Test Crew')
|
||||
expect(json['crew']['gamertag']).to eq('TEST')
|
||||
|
||||
expect(user.reload.crew).to be_present
|
||||
expect(user.active_crew_membership.role).to eq('captain')
|
||||
end
|
||||
|
||||
it 'returns validation error for missing name' do
|
||||
post '/api/v1/crews', params: { crew: { name: '' } }, headers: auth_headers
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user already has a crew' do
|
||||
before do
|
||||
crew = create(:crew)
|
||||
create(:crew_membership, crew: crew, user: user, role: :captain)
|
||||
end
|
||||
|
||||
it 'returns error' do
|
||||
post '/api/v1/crews', params: valid_params, headers: auth_headers
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
json = JSON.parse(response.body)
|
||||
expect(json['code']).to eq('already_in_crew')
|
||||
end
|
||||
end
|
||||
|
||||
context 'without authentication' do
|
||||
it 'returns unauthorized' do
|
||||
post '/api/v1/crews', params: valid_params
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v1/crew' do
|
||||
context 'when user has a crew' do
|
||||
let(:crew) { create(:crew, name: 'My Crew') }
|
||||
let!(:membership) { create(:crew_membership, crew: crew, user: user, role: :captain) }
|
||||
|
||||
it 'returns the crew' do
|
||||
get '/api/v1/crew', headers: auth_headers
|
||||
expect(response).to have_http_status(:ok)
|
||||
json = JSON.parse(response.body)
|
||||
expect(json['crew']['name']).to eq('My Crew')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user has no crew' do
|
||||
it 'returns not found' do
|
||||
get '/api/v1/crew', headers: auth_headers
|
||||
expect(response).to have_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PUT /api/v1/crew' do
|
||||
let(:crew) { create(:crew, name: 'Original Name') }
|
||||
|
||||
context 'as captain' do
|
||||
let!(:membership) { create(:crew_membership, crew: crew, user: user, role: :captain) }
|
||||
|
||||
it 'updates the crew' do
|
||||
put '/api/v1/crew', params: { crew: { name: 'New Name' } }, headers: auth_headers
|
||||
expect(response).to have_http_status(:ok)
|
||||
json = JSON.parse(response.body)
|
||||
expect(json['crew']['name']).to eq('New Name')
|
||||
end
|
||||
end
|
||||
|
||||
context 'as vice captain' do
|
||||
let!(:membership) { create(:crew_membership, crew: crew, user: user, role: :vice_captain) }
|
||||
|
||||
it 'updates the crew' do
|
||||
put '/api/v1/crew', params: { crew: { description: 'Updated' } }, headers: auth_headers
|
||||
expect(response).to have_http_status(:ok)
|
||||
end
|
||||
end
|
||||
|
||||
context 'as regular member' do
|
||||
let!(:membership) { create(:crew_membership, crew: crew, user: user, role: :member) }
|
||||
|
||||
it 'returns unauthorized' do
|
||||
put '/api/v1/crew', params: { crew: { name: 'New Name' } }, headers: auth_headers
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v1/crew/members' do
|
||||
let(:crew) { create(:crew) }
|
||||
let!(:captain_membership) { create(:crew_membership, crew: crew, user: user, role: :captain) }
|
||||
let!(:member1) { create(:crew_membership, crew: crew, role: :member) }
|
||||
let!(:member2) { create(:crew_membership, crew: crew, role: :vice_captain) }
|
||||
|
||||
it 'returns all active crew members' do
|
||||
get '/api/v1/crew/members', headers: auth_headers
|
||||
expect(response).to have_http_status(:ok)
|
||||
json = JSON.parse(response.body)
|
||||
expect(json['members'].length).to eq(3)
|
||||
end
|
||||
|
||||
it 'excludes retired members' do
|
||||
member1.retire!
|
||||
get '/api/v1/crew/members', headers: auth_headers
|
||||
json = JSON.parse(response.body)
|
||||
expect(json['members'].length).to eq(2)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /api/v1/crew/leave' do
|
||||
let(:crew) { create(:crew) }
|
||||
|
||||
context 'as regular member' do
|
||||
let!(:membership) { create(:crew_membership, crew: crew, user: user, role: :member) }
|
||||
|
||||
it 'leaves the crew' do
|
||||
post '/api/v1/crew/leave', headers: auth_headers
|
||||
expect(response).to have_http_status(:no_content)
|
||||
expect(membership.reload.retired).to be true
|
||||
expect(user.reload.crew).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'as vice captain' do
|
||||
let!(:membership) { create(:crew_membership, crew: crew, user: user, role: :vice_captain) }
|
||||
|
||||
it 'leaves the crew' do
|
||||
post '/api/v1/crew/leave', headers: auth_headers
|
||||
expect(response).to have_http_status(:no_content)
|
||||
end
|
||||
end
|
||||
|
||||
context 'as captain' do
|
||||
let!(:membership) { create(:crew_membership, crew: crew, user: user, role: :captain) }
|
||||
|
||||
it 'returns error' do
|
||||
post '/api/v1/crew/leave', headers: auth_headers
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
json = JSON.parse(response.body)
|
||||
expect(json['code']).to eq('captain_cannot_leave')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when not in a crew' do
|
||||
it 'returns error' do
|
||||
post '/api/v1/crew/leave', headers: auth_headers
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
json = JSON.parse(response.body)
|
||||
expect(json['code']).to eq('not_in_crew')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /api/v1/crews/:id/transfer_captain' do
|
||||
let(:crew) { create(:crew) }
|
||||
let(:new_captain) { create(:user) }
|
||||
let!(:captain_membership) { create(:crew_membership, crew: crew, user: user, role: :captain) }
|
||||
let!(:new_captain_membership) { create(:crew_membership, crew: crew, user: new_captain, role: :member) }
|
||||
|
||||
context 'as captain' do
|
||||
it 'transfers captainship to another member' do
|
||||
post "/api/v1/crews/#{crew.id}/transfer_captain",
|
||||
params: { user_id: new_captain.id },
|
||||
headers: auth_headers
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(captain_membership.reload.role).to eq('vice_captain')
|
||||
expect(new_captain_membership.reload.role).to eq('captain')
|
||||
end
|
||||
|
||||
it 'returns error for non-existent member' do
|
||||
post "/api/v1/crews/#{crew.id}/transfer_captain",
|
||||
params: { user_id: SecureRandom.uuid },
|
||||
headers: auth_headers
|
||||
|
||||
expect(response).to have_http_status(:not_found)
|
||||
json = JSON.parse(response.body)
|
||||
expect(json['code']).to eq('member_not_found')
|
||||
end
|
||||
end
|
||||
|
||||
context 'as vice captain' do
|
||||
let!(:captain_membership) { create(:crew_membership, crew: crew, user: user, role: :vice_captain) }
|
||||
|
||||
it 'returns unauthorized' do
|
||||
post "/api/v1/crews/#{crew.id}/transfer_captain",
|
||||
params: { user_id: new_captain.id },
|
||||
headers: auth_headers
|
||||
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Reference in a new issue