# frozen_string_literal: true require 'rails_helper' RSpec.describe ProgrammingGroupsController do render_views let(:user) { create(:admin) } let(:other_user) { create(:external_user) } let(:exercise) { create(:math) } let(:exercise_id) { exercise.id } let(:programming_group) { create(:programming_group, exercise:, users: [user, other_user]) } before { allow(controller).to receive(:current_user).and_return(user) } describe 'POST #create' do let(:user_to_params) { ->(users) { users.map(&:id_with_type).join(', ') } } let(:perform_request) { proc { post :create, params: {exercise_id:, programming_group: pg_params} } } context 'with a valid programming group' do let(:pg_params) { {programming_partner_ids: user_to_params.call([other_user])} } context 'when the request is performed' do before { perform_request.call } expect_assigns(exercise: :exercise, programming_group: ProgrammingGroup) expect_redirect(ProgrammingGroup.last) end it 'creates the programming group' do expect { perform_request.call }.to change(ProgrammingGroup, :count).by(1).and change(ProgrammingGroupMembership, :count).by(2) end it 'does not create a new event' do expect { perform_request.call }.not_to change(Event, :count) end it 'updates the status of the initiating waiting user' do pp_waiting_user = PairProgrammingWaitingUser.create(user:, exercise:, status: :waiting) expect { perform_request.call && pp_waiting_user.reload }.to change(pp_waiting_user, :status).from('waiting').to('created_pg') end it 'updates the status of other waiting users' do pp_waiting_user = PairProgrammingWaitingUser.create(user: other_user, exercise:, status: :waiting) expect { perform_request.call && pp_waiting_user.reload }.to change(pp_waiting_user, :status).from('waiting').to('invited_to_pg') end it 'stores the programming group ID in the session' do allow(controller.session).to receive(:[]=).and_call_original perform_request.call expect(controller.session).to have_received(:[]=).with(:pg_id, ProgrammingGroup.last.id) end end context 'with an invalid programming group' do let(:pg_params) { {} } before { post :create, params: {exercise_id:, programming_group: pg_params} } expect_assigns(exercise: :exercise, programming_group: ProgrammingGroup) expect_http_status(:ok) expect_template(:new) it 'does not create a new programming group' do expect { perform_request.call }.not_to change(ProgrammingGroup, :count) end it 'creates a new event' do expect { perform_request.call }.to change(Event, :count).by(1) end it 'does not update the status of the initiating waiting user' do pp_waiting_user = PairProgrammingWaitingUser.create(user:, exercise:, status: :waiting) expect { perform_request.call && pp_waiting_user.reload }.not_to change(pp_waiting_user, :status) end it 'does not update the status of other waiting users' do pp_waiting_user = PairProgrammingWaitingUser.create(user: other_user, exercise:, status: :waiting) expect { perform_request.call && pp_waiting_user.reload }.not_to change(pp_waiting_user, :status) end end context 'with a duplicated membership' do let(:pg_params) { {programming_partner_ids: user_to_params.call(programming_group.users)} } before { programming_group.save! } context 'when the request is performed' do before { perform_request.call } expect_assigns(exercise: :exercise, programming_group: ProgrammingGroup) expect_http_status(:ok) expect_template(:new) end it 'does not create a new programming group' do expect { perform_request.call }.not_to change(ProgrammingGroup, :count) end it 'creates a new event' do expect { perform_request.call }.to change(Event, :count).by(1) end it 'does not update the status of the initiating waiting user' do pp_waiting_user = PairProgrammingWaitingUser.create(user:, exercise:, status: :waiting) expect { perform_request.call && pp_waiting_user.reload }.not_to change(pp_waiting_user, :status) end it 'does not update the status of other waiting users' do pp_waiting_user = PairProgrammingWaitingUser.create(user: other_user, exercise:, status: :waiting) expect { perform_request.call && pp_waiting_user.reload }.not_to change(pp_waiting_user, :status) end end context 'with a user providing their own ID' do let(:pg_params) { {programming_partner_ids: user_to_params.call([user, other_user])} } context 'when the request is performed' do before { perform_request.call } expect_assigns(exercise: :exercise, programming_group: ProgrammingGroup) expect_redirect(ProgrammingGroup.last) end it 'creates a new programming group' do expect { perform_request.call }.to change(ProgrammingGroup, :count).by(1).and change(ProgrammingGroupMembership, :count).by(2) end it 'does not create a new event' do expect { perform_request.call }.not_to change(Event, :count) end it 'updates the status of the initiating waiting user' do pp_waiting_user = PairProgrammingWaitingUser.create(user:, exercise:, status: :waiting) expect { perform_request.call && pp_waiting_user.reload }.to change(pp_waiting_user, :status).from('waiting').to('created_pg') end it 'updates the status of other waiting users' do pp_waiting_user = PairProgrammingWaitingUser.create(user: other_user, exercise:, status: :waiting) expect { perform_request.call && pp_waiting_user.reload }.to change(pp_waiting_user, :status).from('waiting').to('invited_to_pg') end it 'stores the programming group ID in the session' do allow(controller.session).to receive(:[]=).and_call_original perform_request.call expect(controller.session).to have_received(:[]=).with(:pg_id, ProgrammingGroup.last.id) end end context 'with invalid programming partner IDs' do let(:pg_params) { {programming_partner_ids: 'test1234'} } before { post :create, params: {exercise_id:, programming_group: pg_params} } expect_assigns(exercise: :exercise, programming_group: ProgrammingGroup) expect_http_status(:ok) expect_template(:new) it 'does not create a new programming group' do expect { perform_request.call }.not_to change(ProgrammingGroup, :count) end it 'creates a new event' do expect { perform_request.call }.to change(Event, :count).by(1) end end context 'with too many users' do let(:third_user) { create(:external_user) } let(:pg_params) { {programming_partner_ids: user_to_params.call([other_user, third_user])} } before { post :create, params: {exercise_id:, programming_group: pg_params} } expect_assigns(exercise: :exercise, programming_group: ProgrammingGroup) expect_http_status(:ok) expect_template(:new) it 'does not create a new programming group' do expect { perform_request.call }.not_to change(ProgrammingGroup, :count) end it 'creates a new event' do expect { perform_request.call }.to change(Event, :count).by(1) end end end describe 'DELETE #destroy' do before { delete :destroy, params: {id: programming_group.id} } expect_assigns(programming_group: ProgrammingGroup) it 'destroys the programming group' do programming_group = create(:programming_group) expect { delete :destroy, params: {id: programming_group.id} }.to change(ProgrammingGroup, :count).by(-1) end it 'removes the programming group ID from the session' do # Setup: Construct a programming group and set it as the current group in the session. programming_group = create(:programming_group, users: [user, other_user]) allow(controller.session).to receive(:[]).and_call_original allow(controller.session).to receive(:[]).with(:pg_id).and_return programming_group.id # Test: Destroy the programming group and verify that it is no longer retained in the session. allow(controller.session).to receive(:delete).and_call_original delete :destroy, params: {id: programming_group.id} expect(controller.session).to have_received(:delete).with(:pg_id) end expect_redirect(:programming_groups) end describe 'GET #edit' do before { get :edit, params: {id: programming_group.id} } expect_assigns(programming_group: ProgrammingGroup) expect_http_status(:ok) expect_template(:edit) end describe 'GET #index' do before do create_pair(:programming_group) get :index end expect_assigns(programming_groups: ProgrammingGroup.all) expect_http_status(:ok) expect_template(:index) end describe 'GET #new' do let(:perform_request) { proc { get :new, params: {exercise_id:} } } context 'when the request is performed' do before { perform_request.call } expect_assigns(programming_group: ProgrammingGroup) expect_http_status(:ok) expect_template(:new) end it 'creates a new event' do expect { perform_request.call }.to change(Event, :count).by(1) end context 'with an existing programming group' do let(:programming_group) { create(:programming_group, exercise:, users: [user, other_user]) } before { programming_group.save! } context 'when the request is performed' do before { perform_request.call } expect_redirect { implement_exercise_path(exercise) } it 'stores the programming group ID in the session' do allow(controller.session).to receive(:[]=).and_call_original perform_request.call expect(controller.session).to have_received(:[]=).with(:pg_id, programming_group.id) end end context 'when the user has already started the exercise' do before { create(:submission, exercise:, user:) } context 'when the request is performed' do before { perform_request.call } expect_redirect { implement_exercise_path(exercise) } it 'does not store the programming group ID in the session' do allow(controller.session).to receive(:[]=).and_call_original perform_request.call expect(controller.session).not_to have_received(:[]=).with(:pg_id) end end end end end describe 'GET #show' do before { get :show, params: {id: programming_group.id} } expect_assigns(programming_group: :programming_group) expect_http_status(:ok) expect_template(:show) end describe 'PUT #update' do let(:perform_request) { proc { put :update, params: {programming_group: pg_params, id: programming_group.id} } } before do # In order to test a successful update, we need to remove a user from the programming group. # Since otherwise the group size is fixed to exactly two members, we temporarily allow a larger group size. allow_any_instance_of(ProgrammingGroup).to receive(:max_group_size).and_return(true) # The programming group needs to be saved, otherwise we cannot attempt to update it. programming_group.save! end context 'with a valid programming group' do let(:programming_group) { create(:programming_group, exercise:, users: create_list(:external_user, 3)) } let(:pg_params) { {programming_group_membership_ids: programming_group.programming_group_memberships.map(&:id)[0..1]} } context 'when the request is performed' do before { perform_request.call } expect_assigns(programming_group: ProgrammingGroup) expect_redirect(:programming_group) end it 'does not update the programming group' do expect { perform_request.call }.not_to change(ProgrammingGroup, :count) end it 'removes the desired programming group membership' do expect { perform_request.call }.to change(ProgrammingGroupMembership, :count).by(-1) end it 'does not update any existing programming group membership' do expect { perform_request.call && programming_group.programming_group_memberships.first.reload }.not_to change(programming_group.programming_group_memberships.first, :updated_at) end end context 'with an invalid programming group' do let(:pg_params) { {programming_group_membership_ids: []} } context 'when the request is performed' do before { perform_request.call } expect_assigns(programming_group: ProgrammingGroup) expect_http_status(:ok) expect_template(:edit) end it 'does not update the programming group' do expect { perform_request.call }.not_to change(ProgrammingGroup, :count) end it 'does not update the programming group memberships' do expect { perform_request.call }.not_to change(ProgrammingGroupMembership, :count) end end end end