From d8a9b27ab3f198ec984085c723d81aa27d7daf9b Mon Sep 17 00:00:00 2001 From: Malaber <32635600+Malaber@users.noreply.github.com> Date: Thu, 6 Jun 2019 15:48:02 +0200 Subject: [PATCH 01/17] Add test type to all interactor tests --- .../add_group_stage_to_tournament_interactor_spec.rb | 2 +- spec/interactors/add_playoffs_to_tournament_interactor_spec.rb | 2 +- spec/interactors/populate_match_below_interactor_spec.rb | 2 +- ...ave_application_record_object_to_database_interactor_spec.rb | 2 +- spec/interactors/update_groups_group_scores_interactor_spec.rb | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/interactors/add_group_stage_to_tournament_interactor_spec.rb b/spec/interactors/add_group_stage_to_tournament_interactor_spec.rb index 6e009df..6df62e5 100644 --- a/spec/interactors/add_group_stage_to_tournament_interactor_spec.rb +++ b/spec/interactors/add_group_stage_to_tournament_interactor_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe AddGroupStageToTournament do +RSpec.describe AddGroupStageToTournament, type: :interactor do let(:empty_tournament_context) do AddGroupStageToTournament.call(tournament: @empty_tournament, groups: @groups) end diff --git a/spec/interactors/add_playoffs_to_tournament_interactor_spec.rb b/spec/interactors/add_playoffs_to_tournament_interactor_spec.rb index ca60cf9..0ee2cbc 100644 --- a/spec/interactors/add_playoffs_to_tournament_interactor_spec.rb +++ b/spec/interactors/add_playoffs_to_tournament_interactor_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe AddPlayoffsToTournament do +RSpec.describe AddPlayoffsToTournament, type: :interactor do let(:group_stage_tournament_context) do AddPlayoffsToTournament.call(tournament: @group_stage_tournament) end diff --git a/spec/interactors/populate_match_below_interactor_spec.rb b/spec/interactors/populate_match_below_interactor_spec.rb index 8b751e0..6f87038 100644 --- a/spec/interactors/populate_match_below_interactor_spec.rb +++ b/spec/interactors/populate_match_below_interactor_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe PopulateMatchBelow do +RSpec.describe PopulateMatchBelow, type: :interactor do before do @match = create(:match) @objects_to_save = [create(:match), create_list(:match_score, 2)] diff --git a/spec/interactors/save_application_record_object_to_database_interactor_spec.rb b/spec/interactors/save_application_record_object_to_database_interactor_spec.rb index ce2edb3..46f6d72 100644 --- a/spec/interactors/save_application_record_object_to_database_interactor_spec.rb +++ b/spec/interactors/save_application_record_object_to_database_interactor_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe SaveApplicationRecordObject do +RSpec.describe SaveApplicationRecordObject, type: :interactor do before do @tournament = create(:tournament) end diff --git a/spec/interactors/update_groups_group_scores_interactor_spec.rb b/spec/interactors/update_groups_group_scores_interactor_spec.rb index 83d1068..6db8ba9 100644 --- a/spec/interactors/update_groups_group_scores_interactor_spec.rb +++ b/spec/interactors/update_groups_group_scores_interactor_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe UpdateGroupsGroupScores do +RSpec.describe UpdateGroupsGroupScores, type: :interactor do before do @group = create(:group) @group_scores = create_list(:group_score, 2) From f5b610703cc3acff9f9201766c6a5ff14f4650a9 Mon Sep 17 00:00:00 2001 From: Malaber <32635600+Malaber@users.noreply.github.com> Date: Sun, 9 Jun 2019 11:46:29 +0200 Subject: [PATCH 02/17] Add two new parameters to tournament instant_finalists_amount is the amount of teams that instantly are in the finals after the group stage ends. intermediate_round_participants_amount is the amount of teams that have a chance to advance after group stage but must play a relegation game to actually do so. Both of these values need to match, so that instant_finalists_amount + (intermediate_round_participants_amount / 2) is a power of 2. Or to be more precise, the power of two that is saved in playoff_teams_amount of the tournament. --- app/serializers/tournament_serializer.rb | 3 ++- db/migrate/0000_create_schema.rb | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/serializers/tournament_serializer.rb b/app/serializers/tournament_serializer.rb index 9805797..25582e1 100644 --- a/app/serializers/tournament_serializer.rb +++ b/app/serializers/tournament_serializer.rb @@ -1,7 +1,8 @@ # frozen_string_literal: true class TournamentSerializer < SimpleTournamentSerializer - attributes :description + attributes :description, :playoff_teams_amount, + :instant_finalists_amount, :intermediate_round_participants_amount has_many :stages has_many :teams diff --git a/db/migrate/0000_create_schema.rb b/db/migrate/0000_create_schema.rb index f1d4a3f..14ffb52 100644 --- a/db/migrate/0000_create_schema.rb +++ b/db/migrate/0000_create_schema.rb @@ -52,6 +52,8 @@ class CreateSchema < ActiveRecord::Migration[5.2] t.string :description t.boolean :public, default: true t.integer :playoff_teams_amount + t.integer :instant_finalists_amount, default: 0 + t.integer :intermediate_round_participants_amount, default: 0 # relation to owner t.belongs_to :user, index: true, null: false, foreign_key: { on_delete: :cascade } From 4d5d7bc8123aceed53e9357440bccf4acc5ded15 Mon Sep 17 00:00:00 2001 From: Malaber <32635600+Malaber@users.noreply.github.com> Date: Sun, 9 Jun 2019 17:23:06 +0200 Subject: [PATCH 03/17] Only allow positive powers of two for playoff_teams_amount --- app/models/tournament.rb | 9 ++++++++ db/migrate/0000_create_schema.rb | 2 +- .../tournaments_controller_spec.rb | 23 +++++++++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/app/models/tournament.rb b/app/models/tournament.rb index 3813f08..ec71609 100644 --- a/app/models/tournament.rb +++ b/app/models/tournament.rb @@ -10,6 +10,8 @@ class Tournament < ApplicationRecord validates :name, presence: true validates :code, presence: true, uniqueness: true + validate :playoff_teams_amount_is_positive_power_of_two + alias_attribute :owner, :user after_initialize :generate_code @@ -24,4 +26,11 @@ class Tournament < ApplicationRecord break if errors['code'].blank? end end + + def playoff_teams_amount_is_positive_power_of_two + return if (Utils.po2?(playoff_teams_amount) && playoff_teams_amount.positive?) || playoff_teams_amount.zero? + + errors.add(:playoff_teams_amount, + 'playoff_teams_amount needs to be a positive power of two') + end end diff --git a/db/migrate/0000_create_schema.rb b/db/migrate/0000_create_schema.rb index 14ffb52..390edfa 100644 --- a/db/migrate/0000_create_schema.rb +++ b/db/migrate/0000_create_schema.rb @@ -51,7 +51,7 @@ class CreateSchema < ActiveRecord::Migration[5.2] t.string :code, null: false, index: { unique: true } t.string :description t.boolean :public, default: true - t.integer :playoff_teams_amount + t.integer :playoff_teams_amount, default: 0 t.integer :instant_finalists_amount, default: 0 t.integer :intermediate_round_participants_amount, default: 0 diff --git a/spec/controllers/tournaments_controller_spec.rb b/spec/controllers/tournaments_controller_spec.rb index c3313bd..c0e1353 100644 --- a/spec/controllers/tournaments_controller_spec.rb +++ b/spec/controllers/tournaments_controller_spec.rb @@ -194,6 +194,29 @@ RSpec.describe TournamentsController, type: :controller do expect(@group_stage_tournament.playoff_teams_amount) .to eq(create_group_tournament_data[:playoff_teams_amount]) end + + context 'playoff_teams_amount unacceptable' do + it 'is not a power of two' do + post :create, params: create_group_tournament_data.merge(playoff_teams_amount: 18) + expect(response).to have_http_status(:unprocessable_entity) + expect(deserialize_response(response).values.first.first) + .to eq('playoff_teams_amount needs to be a positive power of two') + end + + it 'isn\'t positive' do + post :create, params: create_group_tournament_data.merge(playoff_teams_amount: -16) + expect(response).to have_http_status(:unprocessable_entity) + expect(deserialize_response(response).values.first.first) + .to eq('playoff_teams_amount needs to be a positive power of two') + end + + it 'isn\'t positive nor a power of two' do + post :create, params: create_group_tournament_data.merge(playoff_teams_amount: -42) + expect(response).to have_http_status(:unprocessable_entity) + expect(deserialize_response(response).values.first.first) + .to eq('playoff_teams_amount needs to be a positive power of two') + end + end end it 'renders a JSON response with the new tournament' do From f04c11427a59bb69e6bba799b9ae16e9ad8b6827 Mon Sep 17 00:00:00 2001 From: Malaber <32635600+Malaber@users.noreply.github.com> Date: Mon, 10 Jun 2019 19:17:40 +0200 Subject: [PATCH 04/17] Assign default values on Group Stage creation default values are assigned to instant_finalists_amount and intermediate_round_participants_amount depending on playoff_teams_amount and group amount --- .../add_group_stage_to_tournament.rb | 3 +++ app/services/tournament_service.rb | 14 +++++++++++++ ...oup_stage_to_tournament_interactor_spec.rb | 20 +++++++++++++++++-- 3 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 app/services/tournament_service.rb diff --git a/app/interactors/add_group_stage_to_tournament.rb b/app/interactors/add_group_stage_to_tournament.rb index d1d28e2..23e81de 100644 --- a/app/interactors/add_group_stage_to_tournament.rb +++ b/app/interactors/add_group_stage_to_tournament.rb @@ -10,6 +10,9 @@ class AddGroupStageToTournament begin group_stage = GroupStageService.generate_group_stage(groups) tournament.stages = [group_stage] + tournament.instant_finalists_amount, tournament.intermediate_round_participants_amount = + TournamentService.calculate_default_amount_of_teams_advancing(tournament.playoff_teams_amount, + group_stage.groups.size) context.object_to_save = tournament rescue StandardError context.fail! diff --git a/app/services/tournament_service.rb b/app/services/tournament_service.rb new file mode 100644 index 0000000..b01ef02 --- /dev/null +++ b/app/services/tournament_service.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +class TournamentService + class << self + def calculate_default_amount_of_teams_advancing(playoff_teams_amount, amount_of_groups) + # the amount of whole places that advance in a group (e. g. all 1rst places of every group instantly go through) + instant_finalists_amount = (playoff_teams_amount.floor / amount_of_groups.floor) * amount_of_groups.floor + # the amount of teams that still need to play an intermediate round before advancing to playoffs + intermediate_round_participants_amount = (playoff_teams_amount - instant_finalists_amount) * 2 + + [instant_finalists_amount, intermediate_round_participants_amount] + end + end +end diff --git a/spec/interactors/add_group_stage_to_tournament_interactor_spec.rb b/spec/interactors/add_group_stage_to_tournament_interactor_spec.rb index 6df62e5..1b4f90b 100644 --- a/spec/interactors/add_group_stage_to_tournament_interactor_spec.rb +++ b/spec/interactors/add_group_stage_to_tournament_interactor_spec.rb @@ -14,6 +14,7 @@ RSpec.describe AddGroupStageToTournament, type: :interactor do @group_stage_tournament = create(:group_stage_tournament, stage_count: 0, group_count: 0) @group_stage = create(:group_stage) @groups = Hash[1 => create_list(:team, 4), 2 => create_list(:team, 4)].values + @tournament_service_defaults = [78_345, 2_387] end context 'GroupStageService mocked' do @@ -24,13 +25,28 @@ RSpec.describe AddGroupStageToTournament, type: :interactor do end context 'empty tournament' do + before do + allow(class_double('TournamentService').as_stubbed_const(transfer_nested_constants: true)) + .to receive(:calculate_default_amount_of_teams_advancing) + .with(@empty_tournament.playoff_teams_amount, @group_stage.groups.size) + .and_return(@tournament_service_defaults) + end + it 'succeeds' do expect(empty_tournament_context).to be_a_success end it 'adds group stage to the tournament' do - test = empty_tournament_context.tournament.stages.first - expect(test).to eq(@group_stage) + expect(empty_tournament_context.tournament.stages.first).to eq(@group_stage) + end + + it 'sets default for instant_finalists_amount' do + expect(empty_tournament_context.tournament.instant_finalists_amount).to eq(@tournament_service_defaults.first) + end + + it 'sets default for intermediate_round_participants_amount' do + expect(empty_tournament_context.tournament.intermediate_round_participants_amount) + .to eq(@tournament_service_defaults.second) end end end From 36bdfbae2853b100aa1efd1b4d50c21ecb0ab62c Mon Sep 17 00:00:00 2001 From: Malaber <32635600+Malaber@users.noreply.github.com> Date: Mon, 10 Jun 2019 20:13:43 +0200 Subject: [PATCH 05/17] Add default for playoff_teams_amount to tournament factory --- spec/factories/tournaments.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/factories/tournaments.rb b/spec/factories/tournaments.rb index b4a522a..68f6566 100644 --- a/spec/factories/tournaments.rb +++ b/spec/factories/tournaments.rb @@ -10,6 +10,7 @@ FactoryBot.define do end after(:create) do |tournament, evaluator| tournament.teams = create_list(:team, evaluator.teams_count, tournament: tournament) + tournament.playoff_teams_amount = (tournament.teams.size / 2) end factory :stage_tournament do From 6a060651a4101301ff1664800c576313efc4ca0a Mon Sep 17 00:00:00 2001 From: Malaber <32635600+Malaber@users.noreply.github.com> Date: Mon, 10 Jun 2019 21:39:04 +0200 Subject: [PATCH 06/17] Validate update parameters playoff_teams_amount, instant_finalists_amount and intermediate_round_participants_amount need to make sense together --- app/controllers/tournaments_controller.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/controllers/tournaments_controller.rb b/app/controllers/tournaments_controller.rb index d11667d..c388772 100644 --- a/app/controllers/tournaments_controller.rb +++ b/app/controllers/tournaments_controller.rb @@ -5,6 +5,7 @@ class TournamentsController < ApplicationController before_action :authenticate_user!, only: %i[create update destroy] before_action -> { require_owner! @tournament.owner }, only: %i[update destroy] before_action :validate_create_params, only: %i[create] + before_action :validate_update_params, only: %i[update] rescue_from ActiveRecord::RecordNotFound, with: :render_not_found_error # GET /tournaments @@ -113,4 +114,17 @@ class TournamentsController < ApplicationController render json: { error: 'Invalid teams array' }, status: :unprocessable_entity end + + def validate_update_params + playoff_teams_amount = params['playoff_teams_amount'] || @tournament.playoff_teams_amount + instant_finalists_amount = params['instant_finalists_amount'] || @tournament.instant_finalists_amount + intermediate_round_participants_amount = params['intermediate_round_participants_amount'] || + @tournament.intermediate_round_participants_amount + return if instant_finalists_amount + (intermediate_round_participants_amount / 2) == + playoff_teams_amount + + render json: { + error: 'playoff_teams_amount, instant_finalists_amount and intermediate_round_participants_amount don\'t match' + }, status: :unprocessable_entity + end end From b408d1dd377c1f8262e8445e6e11ee18602df7d8 Mon Sep 17 00:00:00 2001 From: Malaber <32635600+Malaber@users.noreply.github.com> Date: Tue, 11 Jun 2019 11:32:44 +0200 Subject: [PATCH 07/17] Ignore validity of matching params if only playoff_teams_amount changed If only playoff_teams_amount changed, the other two will be overwritten anyways. --- app/controllers/tournaments_controller.rb | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/controllers/tournaments_controller.rb b/app/controllers/tournaments_controller.rb index c388772..37d376d 100644 --- a/app/controllers/tournaments_controller.rb +++ b/app/controllers/tournaments_controller.rb @@ -115,11 +115,20 @@ class TournamentsController < ApplicationController render json: { error: 'Invalid teams array' }, status: :unprocessable_entity end + def only_playoff_teams_amount_changed + params['playoff_teams_amount'] && + params['instant_finalists_amount'].nil? && + params['intermediate_round_participants_amount'].nil? + end + def validate_update_params + return if only_playoff_teams_amount_changed + playoff_teams_amount = params['playoff_teams_amount'] || @tournament.playoff_teams_amount instant_finalists_amount = params['instant_finalists_amount'] || @tournament.instant_finalists_amount intermediate_round_participants_amount = params['intermediate_round_participants_amount'] || - @tournament.intermediate_round_participants_amount + @tournament.intermediate_round_participants_amount + return if instant_finalists_amount + (intermediate_round_participants_amount / 2) == playoff_teams_amount From 3e04584e9fe35af532bdc5f6805a0d639822fa5b Mon Sep 17 00:00:00 2001 From: Malaber <32635600+Malaber@users.noreply.github.com> Date: Tue, 11 Jun 2019 15:52:46 +0200 Subject: [PATCH 08/17] Test changing group_stage transition relevant parameters --- .../tournaments_controller_spec.rb | 94 +++++++++++++++++++ spec/factories/tournaments.rb | 2 + 2 files changed, 96 insertions(+) diff --git a/spec/controllers/tournaments_controller_spec.rb b/spec/controllers/tournaments_controller_spec.rb index c0e1353..4855d63 100644 --- a/spec/controllers/tournaments_controller_spec.rb +++ b/spec/controllers/tournaments_controller_spec.rb @@ -305,6 +305,100 @@ RSpec.describe TournamentsController, type: :controller do expect(response).to have_http_status(:ok) expect(response.content_type).to eq('application/json') end + + context 'any variable relevant for group stage to playoff transition changed' do + before(:each) do + @filled_tournament = create(:group_stage_tournament) + apply_authentication_headers_for @filled_tournament.owner + end + + it 'fails when only instant_finalists_amount is changed' do + put :update, params: { id: @filled_tournament.to_param }.merge(instant_finalists_amount: 29) + expect(response).to have_http_status(:unprocessable_entity) + end + + it 'fails when only intermediate_round_participants_amount is changed' do + put :update, params: { id: @filled_tournament.to_param }.merge(intermediate_round_participants_amount: 29) + expect(response).to have_http_status(:unprocessable_entity) + end + + it 'fails when parameters don\'t match' do + put :update, params: { id: @filled_tournament.to_param }.merge(intermediate_round_participants_amount: 29, + instant_finalists_amount: 32) + expect(response).to have_http_status(:unprocessable_entity) + end + + it 'succeeds when all three are changed correctly' do + put :update, params: { id: @filled_tournament.to_param }.merge(intermediate_round_participants_amount: 2, + instant_finalists_amount: 1, + playoff_teams_amount: 2) + end + + context 'only playoff_teams_amount is changed reasonably but update fails' do + before do + allow_any_instance_of(Tournament) + .to receive(:update) + .and_return(false) + end + + it 'returns unprocessable entity' do + put :update, params: { id: @filled_tournament.to_param }.merge(playoff_teams_amount: 8) + expect(response).to have_http_status(:unprocessable_entity) + end + + it 'doesn\'t change playoff_teams_amount' do + expect do + put :update, params: { id: @filled_tournament.to_param }.merge(playoff_teams_amount: 8) + @filled_tournament.reload + end + .to_not(change { @filled_tournament.playoff_teams_amount }) + end + + it 'doesn\'t change instant_finalists_amount' do + expect do + put :update, params: { id: @filled_tournament.to_param }.merge(playoff_teams_amount: 8) + @filled_tournament.reload + end + .to_not(change { @filled_tournament.instant_finalists_amount }) + end + + it 'doesn\'t change intermediate_round_participants_amount' do + expect do + put :update, params: { id: @filled_tournament.to_param }.merge(playoff_teams_amount: 8) + @filled_tournament.reload + end + .to_not(change { @filled_tournament.intermediate_round_participants_amount }) + end + end + + context 'only playoff_teams_amount is changed to something reasonable' do + before do + put :update, params: { id: @filled_tournament.to_param }.merge(playoff_teams_amount: 8) + @filled_tournament.reload + end + + it 'succeeds' do + expect(response).to have_http_status(:ok) + end + + it 'changes playoff_teams_amount' do + expect(@filled_tournament.playoff_teams_amount).to eq(8) + end + + it 'adapts instant_finalists_amount' do + expect(@filled_tournament.instant_finalists_amount).to eq(8) + end + + it 'adapts intermediate_round_participants_amount' do + expect(@filled_tournament.intermediate_round_participants_amount).to eq(0) + end + end + + it 'fails when playoff_teams_amount is higher than the amount of teams participating' do + put :update, params: { id: @filled_tournament.to_param }.merge(playoff_teams_amount: 783) + expect(response).to have_http_status(:unprocessable_entity) + end + end end context 'as another user' do diff --git a/spec/factories/tournaments.rb b/spec/factories/tournaments.rb index 68f6566..31aa828 100644 --- a/spec/factories/tournaments.rb +++ b/spec/factories/tournaments.rb @@ -11,6 +11,8 @@ FactoryBot.define do after(:create) do |tournament, evaluator| tournament.teams = create_list(:team, evaluator.teams_count, tournament: tournament) tournament.playoff_teams_amount = (tournament.teams.size / 2) + tournament.instant_finalists_amount = tournament.playoff_teams_amount + tournament.intermediate_round_participants_amount = 0 end factory :stage_tournament do From e936fd40b2ea9fc14057b56dbdda043577e76dae Mon Sep 17 00:00:00 2001 From: Malaber <32635600+Malaber@users.noreply.github.com> Date: Tue, 11 Jun 2019 21:44:16 +0200 Subject: [PATCH 09/17] Convert integer parameters to actual integers Apparently rails thinks this is the way to do it. --- app/controllers/tournaments_controller.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/tournaments_controller.rb b/app/controllers/tournaments_controller.rb index 37d376d..cd7ebce 100644 --- a/app/controllers/tournaments_controller.rb +++ b/app/controllers/tournaments_controller.rb @@ -124,9 +124,9 @@ class TournamentsController < ApplicationController def validate_update_params return if only_playoff_teams_amount_changed - playoff_teams_amount = params['playoff_teams_amount'] || @tournament.playoff_teams_amount - instant_finalists_amount = params['instant_finalists_amount'] || @tournament.instant_finalists_amount - intermediate_round_participants_amount = params['intermediate_round_participants_amount'] || + playoff_teams_amount = params['playoff_teams_amount'].to_i || @tournament.playoff_teams_amount + instant_finalists_amount = params['instant_finalists_amount'].to_i || @tournament.instant_finalists_amount + intermediate_round_participants_amount = params['intermediate_round_participants_amount'].to_i || @tournament.intermediate_round_participants_amount return if instant_finalists_amount + (intermediate_round_participants_amount / 2) == From 5004dec39abfbbd698390a8dfdac85ff4dafe68c Mon Sep 17 00:00:00 2001 From: Malaber <32635600+Malaber@users.noreply.github.com> Date: Tue, 11 Jun 2019 21:45:25 +0200 Subject: [PATCH 10/17] Change playoff transition relevant attributes of tournament on update They are only updated when playoff_teams_amount changed alone --- app/controllers/tournaments_controller.rb | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/app/controllers/tournaments_controller.rb b/app/controllers/tournaments_controller.rb index cd7ebce..9fa7f18 100644 --- a/app/controllers/tournaments_controller.rb +++ b/app/controllers/tournaments_controller.rb @@ -64,10 +64,20 @@ class TournamentsController < ApplicationController # PATCH/PUT /tournaments/1 def update - if @tournament.update(tournament_params) - render json: @tournament - else - render json: @tournament.errors, status: :unprocessable_entity + Tournament.transaction do + if only_playoff_teams_amount_changed + @tournament.instant_finalists_amount, @tournament.intermediate_round_participants_amount = + TournamentService.calculate_default_amount_of_teams_advancing( + params['playoff_teams_amount'].to_i, + @tournament.stages.find_by(level: -1).groups.size + ) + end + if @tournament.update(tournament_params) + render json: @tournament + else + render json: @tournament.errors, status: :unprocessable_entity + raise ActiveRecord::Rollback + end end end From 4f64afd5fe7e355f233d2bf2bba5fe36d9b9e9a7 Mon Sep 17 00:00:00 2001 From: Malaber <32635600+Malaber@users.noreply.github.com> Date: Tue, 11 Jun 2019 21:46:02 +0200 Subject: [PATCH 11/17] Assign values to playoff transition relevant variables in factory --- spec/factories/tournaments.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/spec/factories/tournaments.rb b/spec/factories/tournaments.rb index 31aa828..9a8a7d3 100644 --- a/spec/factories/tournaments.rb +++ b/spec/factories/tournaments.rb @@ -11,8 +11,10 @@ FactoryBot.define do after(:create) do |tournament, evaluator| tournament.teams = create_list(:team, evaluator.teams_count, tournament: tournament) tournament.playoff_teams_amount = (tournament.teams.size / 2) - tournament.instant_finalists_amount = tournament.playoff_teams_amount - tournament.intermediate_round_participants_amount = 0 + tournament.instant_finalists_amount = (tournament.playoff_teams_amount / 2) + tournament.intermediate_round_participants_amount = ((tournament.playoff_teams_amount - + tournament.instant_finalists_amount) * 2) + tournament.save! end factory :stage_tournament do From 2e336262aa2593326b018ad72e04f2efd0091e05 Mon Sep 17 00:00:00 2001 From: Malaber <32635600+Malaber@users.noreply.github.com> Date: Thu, 6 Jun 2019 14:06:53 +0200 Subject: [PATCH 12/17] Add state to stage This state is required to stop the group stage and trigger playoff generation, it is (for now) irrelevant for anything other than that. --- app/models/stage.rb | 2 ++ app/serializers/stage_serializer.rb | 2 +- app/services/group_stage_service.rb | 2 +- db/migrate/0000_create_schema.rb | 1 + spec/factories/stages.rb | 1 + 5 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/models/stage.rb b/app/models/stage.rb index 9246352..1f36b21 100644 --- a/app/models/stage.rb +++ b/app/models/stage.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class Stage < ApplicationRecord + enum state: %i[playoff_stage in_progress finished] + belongs_to :tournament has_many :matches, dependent: :destroy has_many :groups, dependent: :destroy diff --git a/app/serializers/stage_serializer.rb b/app/serializers/stage_serializer.rb index dc76a83..56cb6d0 100644 --- a/app/serializers/stage_serializer.rb +++ b/app/serializers/stage_serializer.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class StageSerializer < ApplicationSerializer - attributes :level + attributes :level, :state has_many :matches has_many :groups diff --git a/app/services/group_stage_service.rb b/app/services/group_stage_service.rb index 592f6ad..504ff24 100644 --- a/app/services/group_stage_service.rb +++ b/app/services/group_stage_service.rb @@ -9,7 +9,7 @@ class GroupStageService raise 'Groups need to be equal size' unless (groups.flatten.length.to_f / groups.length.to_f % 1).zero? groups = groups.map(&method(:get_group_object_from)) - Stage.new level: -1, groups: groups + Stage.new level: -1, groups: groups, state: :in_progress end def get_group_object_from(team_array) diff --git a/db/migrate/0000_create_schema.rb b/db/migrate/0000_create_schema.rb index 390edfa..61bba1f 100644 --- a/db/migrate/0000_create_schema.rb +++ b/db/migrate/0000_create_schema.rb @@ -63,6 +63,7 @@ class CreateSchema < ActiveRecord::Migration[5.2] create_table :stages do |t| t.integer :level + t.integer :state, default: 0 t.belongs_to :tournament, index: true, foreign_key: { on_delete: :cascade }, null: false diff --git a/spec/factories/stages.rb b/spec/factories/stages.rb index c2d1e2b..8027cd7 100644 --- a/spec/factories/stages.rb +++ b/spec/factories/stages.rb @@ -5,6 +5,7 @@ FactoryBot.define do tournament factory :group_stage do level { -1 } + state { :in_progress } transient do group_count { 4 } match_factory { :group_match } From 07f5388f6d5b857a784098a1ef7b97d9e3e3669a Mon Sep 17 00:00:00 2001 From: Malaber <32635600+Malaber@users.noreply.github.com> Date: Tue, 11 Jun 2019 23:59:23 +0200 Subject: [PATCH 13/17] Add Tests for TournamentService --- spec/services/tournament_service_spec.rb | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 spec/services/tournament_service_spec.rb diff --git a/spec/services/tournament_service_spec.rb b/spec/services/tournament_service_spec.rb new file mode 100644 index 0000000..65abd35 --- /dev/null +++ b/spec/services/tournament_service_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +RSpec.describe TournamentService do + describe '#calculate_default_amount_of_teams_advancing' do + before do + @instant_finalists_amount, @intermediate_round_participants_amount = + TournamentService.calculate_default_amount_of_teams_advancing(32, 5) + end + + it 'accurately calculates @instant_finalists_amount' do + expect(@instant_finalists_amount).to eq(30) + end + + it 'accurately calculates @intermediate_round_participants_amount' do + expect(@intermediate_round_participants_amount).to eq(4) + end + end +end From c8f69ccb1676672cbcc611d5b390841419f5210f Mon Sep 17 00:00:00 2001 From: Malaber <32635600+Malaber@users.noreply.github.com> Date: Wed, 12 Jun 2019 21:00:54 +0200 Subject: [PATCH 14/17] Dry out tests for unacceptable playoff_teams_amount --- .../tournaments_controller_spec.rb | 43 ++++++++++++------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/spec/controllers/tournaments_controller_spec.rb b/spec/controllers/tournaments_controller_spec.rb index 4855d63..33e985b 100644 --- a/spec/controllers/tournaments_controller_spec.rb +++ b/spec/controllers/tournaments_controller_spec.rb @@ -196,25 +196,38 @@ RSpec.describe TournamentsController, type: :controller do end context 'playoff_teams_amount unacceptable' do - it 'is not a power of two' do - post :create, params: create_group_tournament_data.merge(playoff_teams_amount: 18) - expect(response).to have_http_status(:unprocessable_entity) - expect(deserialize_response(response).values.first.first) - .to eq('playoff_teams_amount needs to be a positive power of two') + shared_examples_for 'wrong playoff_teams_amount' do + it 'fails' do + expect(response).to have_http_status(:unprocessable_entity) + end + it 'returns the correct error message' do + expect(deserialize_response(response).values.first.first) + .to eq('playoff_teams_amount needs to be a positive power of two') + end end - it 'isn\'t positive' do - post :create, params: create_group_tournament_data.merge(playoff_teams_amount: -16) - expect(response).to have_http_status(:unprocessable_entity) - expect(deserialize_response(response).values.first.first) - .to eq('playoff_teams_amount needs to be a positive power of two') + context 'is not a power of two' do + before do + post :create, params: create_group_tournament_data.merge(playoff_teams_amount: 18) + end + + it_should_behave_like 'wrong playoff_teams_amount' end - it 'isn\'t positive nor a power of two' do - post :create, params: create_group_tournament_data.merge(playoff_teams_amount: -42) - expect(response).to have_http_status(:unprocessable_entity) - expect(deserialize_response(response).values.first.first) - .to eq('playoff_teams_amount needs to be a positive power of two') + context 'isn\'t positive' do + before do + post :create, params: create_group_tournament_data.merge(playoff_teams_amount: -16) + end + + it_should_behave_like 'wrong playoff_teams_amount' + end + + context 'isn\'t positive nor a power of two' do + before do + post :create, params: create_group_tournament_data.merge(playoff_teams_amount: -42) + end + + it_should_behave_like 'wrong playoff_teams_amount' end end end From 63db00b9ffca8adc7caa1dffe8fbe00e5e468d03 Mon Sep 17 00:00:00 2001 From: Malaber <32635600+Malaber@users.noreply.github.com> Date: Thu, 13 Jun 2019 13:28:34 +0200 Subject: [PATCH 15/17] Actually test if correct field gets set in error response --- spec/controllers/tournaments_controller_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/controllers/tournaments_controller_spec.rb b/spec/controllers/tournaments_controller_spec.rb index 33e985b..06e05dc 100644 --- a/spec/controllers/tournaments_controller_spec.rb +++ b/spec/controllers/tournaments_controller_spec.rb @@ -201,7 +201,7 @@ RSpec.describe TournamentsController, type: :controller do expect(response).to have_http_status(:unprocessable_entity) end it 'returns the correct error message' do - expect(deserialize_response(response).values.first.first) + expect(deserialize_response(response)[:playoff_teams_amount].first) .to eq('playoff_teams_amount needs to be a positive power of two') end end From bb1b8798d81fa2f0dc7e0afeeb99bdd3b66c8659 Mon Sep 17 00:00:00 2001 From: Malaber <32635600+Malaber@users.noreply.github.com> Date: Thu, 13 Jun 2019 16:09:38 +0200 Subject: [PATCH 16/17] DRY out group_stage_service_spec --- spec/services/group_stage_service_spec.rb | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/spec/services/group_stage_service_spec.rb b/spec/services/group_stage_service_spec.rb index c7a850e..77eadea 100644 --- a/spec/services/group_stage_service_spec.rb +++ b/spec/services/group_stage_service_spec.rb @@ -6,26 +6,26 @@ RSpec.describe GroupStageService do @teams2 = create_list(:team, 4) @prepared_groups = Hash[1 => @teams1, 2 => @teams2].values end - describe '#generate_group_stage method' do + describe '#generate_group_stage' do + let(:prepared_groups_groupstage) do + GroupStageService.generate_group_stage(@prepared_groups) + end + it 'returns a stage object' do - group_stage = GroupStageService.generate_group_stage(@prepared_groups) - expect(group_stage).to be_a(Stage) + expect(prepared_groups_groupstage).to be_a(Stage) end it 'returns a stage object with level -1' do - group_stage_level = GroupStageService.generate_group_stage(@prepared_groups).level - expect(group_stage_level).to be(-1) + expect(prepared_groups_groupstage.level).to be(-1) end it 'adds the provided groups to the stage' do - group_stage_teams = GroupStageService.generate_group_stage(@prepared_groups).teams - expect(group_stage_teams).to match_array(@prepared_groups.flatten) + expect(prepared_groups_groupstage.teams).to match_array(@prepared_groups.flatten) end it 'adds GroupScore objects for every team present in the group' do - group_stage = GroupStageService.generate_group_stage(@prepared_groups) - teams_in_group_scores = group_stage.groups.map { |g| g.group_scores.map(&:team) }.flatten - expect(teams_in_group_scores).to match_array(@prepared_groups.flatten) + expect(prepared_groups_groupstage.groups.map { |g| g.group_scores.map(&:team) }.flatten) + .to match_array(@prepared_groups.flatten) end it 'raises exception when given different sizes of groups' do From 41194ee18109a7f3c25a0194fa645392d33b8951 Mon Sep 17 00:00:00 2001 From: Malaber <32635600+Malaber@users.noreply.github.com> Date: Thu, 13 Jun 2019 16:13:06 +0200 Subject: [PATCH 17/17] Test group_stage state being assigned correctly --- spec/services/group_stage_service_spec.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/spec/services/group_stage_service_spec.rb b/spec/services/group_stage_service_spec.rb index 77eadea..2a41658 100644 --- a/spec/services/group_stage_service_spec.rb +++ b/spec/services/group_stage_service_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe GroupStageService do +RSpec.describe GroupStageService, focus: true do before do @teams1 = create_list(:team, 4) @teams2 = create_list(:team, 4) @@ -15,6 +15,10 @@ RSpec.describe GroupStageService do expect(prepared_groups_groupstage).to be_a(Stage) end + it 'assigns the correct state' do + expect(prepared_groups_groupstage.state).to eq('in_progress') + end + it 'returns a stage object with level -1' do expect(prepared_groups_groupstage.level).to be(-1) end