Merge pull request #56 from turniere/ticket/TURNIERE-246

Fix teams not being put into second stage if part of :single_team match
This commit is contained in:
Thor77 2019-06-17 15:31:42 +02:00 committed by GitHub
commit 30e852aba9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 138 additions and 16 deletions

View File

@ -13,7 +13,7 @@ class AddGroupStageToTournament
tournament.instant_finalists_amount, tournament.intermediate_round_participants_amount = tournament.instant_finalists_amount, tournament.intermediate_round_participants_amount =
TournamentService.calculate_default_amount_of_teams_advancing(tournament.playoff_teams_amount, TournamentService.calculate_default_amount_of_teams_advancing(tournament.playoff_teams_amount,
group_stage.groups.size) group_stage.groups.size)
context.object_to_save = tournament (context.object_to_save ||= []) << tournament
rescue StandardError rescue StandardError
context.fail! context.fail!
end end

View File

@ -12,7 +12,8 @@ class AddPlayoffsToTournament
else else
tournament.stages.concat playoff_stages tournament.stages.concat playoff_stages
end end
context.object_to_save = tournament context.intermediate_stage = tournament.stages.find(&:intermediate_stage?)
(context.object_to_save ||= []) << tournament
else else
context.fail! context.fail!
end end

View File

@ -0,0 +1,16 @@
# frozen_string_literal: true
class AdvanceTeamsInIntermediateStage
include Interactor
def call
intermediate_stage = context.intermediate_stage
return if intermediate_stage.nil?
intermediate_stage.matches.select { |m| m.state == 'single_team' }
.each do |match|
context.fail! unless PopulateMatchBelowAndSave.call(match: match).success?
end
(context.object_to_save ||= []) << intermediate_stage
end
end

View File

@ -7,7 +7,7 @@ class PopulateMatchBelow
match = context.match match = context.match
begin begin
objects_to_save = PlayoffStageService.populate_match_below(match) objects_to_save = PlayoffStageService.populate_match_below(match)
context.object_to_save = objects_to_save (context.object_to_save ||= []) << objects_to_save
rescue StandardError rescue StandardError
context.fail! context.fail!
end end

View File

@ -4,7 +4,7 @@ class UpdateGroupsGroupScores
include Interactor include Interactor
def call def call
context.object_to_save = GroupStageService.update_group_scores(context.group) (context.object_to_save ||= []) << GroupStageService.update_group_scores(context.group)
rescue StandardError rescue StandardError
context.fail! context.fail!
end end

View File

@ -28,6 +28,8 @@ class Match < ApplicationRecord
end end
def winner def winner
return teams.first if single_team?
finished? ? current_leading_team : nil finished? ? current_leading_team : nil
end end

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class Stage < ApplicationRecord class Stage < ApplicationRecord
enum state: %i[playoff_stage in_progress finished] enum state: %i[playoff_stage intermediate_stage in_progress finished]
belongs_to :tournament belongs_to :tournament
has_many :matches, dependent: :destroy has_many :matches, dependent: :destroy

View File

@ -3,5 +3,5 @@
class AddPlayoffsToTournamentAndSave class AddPlayoffsToTournamentAndSave
include Interactor::Organizer include Interactor::Organizer
organize AddPlayoffsToTournament, SaveApplicationRecordObject organize AddPlayoffsToTournament, AdvanceTeamsInIntermediateStage, SaveApplicationRecordObject
end end

View File

@ -45,12 +45,14 @@ class MatchService
end end
# the start point is to compensate for all the teams that are already within a "normal" match # the start point is to compensate for all the teams that are already within a "normal" match
startpoint = matches.size i = team_offset = matches.size
until matches.size >= needed_games until matches.size >= needed_games
# while we do not have enough matches in general we need to fill the array with "single team" matches # while we do not have enough matches in general we need to fill the array with "single team" matches
i = matches.size + startpoint match = Match.new state: :single_team,
match = Match.new state: :single_team, position: i, match_scores: [MatchScore.create(team: teams[i])] position: i,
match_scores: [MatchScore.create(team: teams[i + team_offset])]
matches << match matches << match
i += 1
end end
matches matches
end end

View File

@ -12,12 +12,11 @@ class PlayoffStageService
# initial_matches are the matches in the first stage; this is the only stage filled with teams from the start on # initial_matches are the matches in the first stage; this is the only stage filled with teams from the start on
initial_matches = MatchService.generate_matches(teams) initial_matches = MatchService.generate_matches(teams)
initial_stage = Stage.new level: stage_count - 1, matches: initial_matches initial_stage = Stage.new level: stage_count - 1, matches: initial_matches
initial_stage.state = :intermediate_stage unless initial_stage.matches.find(&:single_team?).nil?
playoffs << initial_stage playoffs << initial_stage
# empty stages are the stages, the tournament is filled with to have the matches ready for later # empty stages are the stages, the tournament is filled with to have the matches ready for later
empty_stages = generate_stages_with_empty_matches(stage_count - 1) empty_stages = generate_stages_with_empty_matches(stage_count - 1)
empty_stages.each do |stage| playoffs.concat empty_stages
playoffs << stage
end
playoffs playoffs
end end

View File

@ -14,6 +14,13 @@ FactoryBot.define do
state { :in_progress } state { :in_progress }
end end
factory :single_team_match do
state { :single_team }
after(:create) do |match|
match.match_scores = [create(:match_score, points: 0)]
end
end
factory :empty_prepared_playoff_match do factory :empty_prepared_playoff_match do
state { :not_ready } state { :not_ready }
end end

View File

@ -0,0 +1,52 @@
# frozen_string_literal: true
RSpec.describe AdvanceTeamsInIntermediateStage do
shared_examples_for 'succeeding context' do
it 'succeeds' do
expect(context).to be_a_success
end
end
shared_examples_for 'failing context' do
it 'fails' do
expect(context).to be_a_failure
end
end
context 'intermediate_stage is nil' do
let(:context) do
AdvanceTeamsInIntermediateStage.call(intermediate_stage: nil)
end
it_behaves_like 'succeeding context'
it 'doesn\'t call PopulateMatchBelow' do
expect(PopulateMatchBelowAndSave).not_to receive(:call)
context
end
end
context 'intermediate_stage is a realistic stage' do
let(:context) do
AdvanceTeamsInIntermediateStage.call(intermediate_stage: create(:playoff_stage, match_type: :single_team_match))
end
context 'PopulateMatchBelow succeeds' do
before do
expect(class_double('PopulateMatchBelowAndSave').as_stubbed_const(transfer_nested_constants: true))
.to receive(:call).exactly(4).times.and_return(double(:context, success?: true))
end
it_behaves_like 'succeeding context'
end
context 'PopulateMatchBelow fails' do
before do
expect(class_double('PopulateMatchBelowAndSave').as_stubbed_const(transfer_nested_constants: true))
.to receive(:call).and_return(double(:context, success?: false))
end
it_behaves_like 'failing context'
end
end
end

View File

@ -21,7 +21,7 @@ RSpec.describe PopulateMatchBelow, type: :interactor do
end end
it 'provides the objects to save' do it 'provides the objects to save' do
expect(context.object_to_save).to match_array(@objects_to_save) expect(context.object_to_save.flatten).to match_array(@objects_to_save.flatten)
end end
end end

View File

@ -22,7 +22,7 @@ RSpec.describe UpdateGroupsGroupScores, type: :interactor do
end end
it 'provides the objects to save' do it 'provides the objects to save' do
expect(context.object_to_save).to eq(@group_scores) expect(context.object_to_save.flatten).to eq(@group_scores)
end end
end end

View File

@ -117,5 +117,16 @@ RSpec.describe MatchService do
it 'raises an exception for for 0 teams' do it 'raises an exception for for 0 teams' do
expect { MatchService.generate_matches([]) }. to raise_error 'Cannot generate Matches without teams' expect { MatchService.generate_matches([]) }. to raise_error 'Cannot generate Matches without teams'
end end
it 'generates matches with consecutive positions' do
MatchService.generate_matches(create_list(:team, 7)).sort_by(&:position).each_with_index do |match, i|
expect(match.position).to eq(i)
end
end
it 'places all given teams into the matches exactly once' do
teams = create_list(:team, 11)
expect(MatchService.generate_matches(teams).map(&:teams).flatten).to match_array(teams)
end
end end
end end

View File

@ -51,7 +51,7 @@ RSpec.describe PlayoffStageService do
end end
end end
describe 'generates playoff stages for' do describe '#generate_playoff_stages' do
[ [
{ team_size: 1, expected_amount_of_playoff_stages: 1 }, { team_size: 1, expected_amount_of_playoff_stages: 1 },
{ team_size: 2, expected_amount_of_playoff_stages: 1 }, { team_size: 2, expected_amount_of_playoff_stages: 1 },
@ -66,7 +66,7 @@ RSpec.describe PlayoffStageService do
{ team_size: 64, expected_amount_of_playoff_stages: 6 }, { team_size: 64, expected_amount_of_playoff_stages: 6 },
{ team_size: 111, expected_amount_of_playoff_stages: 7 } { team_size: 111, expected_amount_of_playoff_stages: 7 }
].each do |parameters| ].each do |parameters|
it "#{parameters[:team_size]} teams" do it "generates playoff stages for #{parameters[:team_size]} teams" do
amount_of_teams = parameters[:team_size] amount_of_teams = parameters[:team_size]
expected_amount_of_playoff_stages = parameters[:expected_amount_of_playoff_stages] expected_amount_of_playoff_stages = parameters[:expected_amount_of_playoff_stages]
teams = build_list(:team, amount_of_teams) teams = build_list(:team, amount_of_teams)
@ -79,6 +79,38 @@ RSpec.describe PlayoffStageService do
end end
end end
end end
describe 'number of teams isn\'t a power of two' do
let(:generated_stages) do
PlayoffStageService.generate_playoff_stages(create_list(:team, 12))
end
let(:intermediate_stage) do
generated_stages.max_by(&:level)
end
it 'generates an intermediate stage at the top level' do
expect(intermediate_stage.state).to eq('intermediate_stage')
end
it 'generates normal playoff_stage state stages elsewhere' do
(generated_stages - [intermediate_stage]).each do |stage|
expect(stage.state).to eq('playoff_stage')
end
end
end
describe 'number of teams is a power of two' do
let(:generated_stages) do
PlayoffStageService.generate_playoff_stages(create_list(:team, 16))
end
it 'generates only normal playoff_stage state stages' do
generated_stages.each do |stage|
expect(stage.state).to eq('playoff_stage')
end
end
end
end end
describe '#populate_match_below' do describe '#populate_match_below' do