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:
commit
30e852aba9
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -3,5 +3,5 @@
|
||||||
class AddPlayoffsToTournamentAndSave
|
class AddPlayoffsToTournamentAndSave
|
||||||
include Interactor::Organizer
|
include Interactor::Organizer
|
||||||
|
|
||||||
organize AddPlayoffsToTournament, SaveApplicationRecordObject
|
organize AddPlayoffsToTournament, AdvanceTeamsInIntermediateStage, SaveApplicationRecordObject
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue