Merge remote-tracking branch 'origin/master' into codex/buildx-multistage-multiarch
This commit is contained in:
commit
4e04aabb12
|
|
@ -1,5 +1,6 @@
|
|||
stages:
|
||||
- build
|
||||
- e2e
|
||||
- test
|
||||
- deploy
|
||||
|
||||
|
|
@ -68,6 +69,8 @@ build test image:
|
|||
|
||||
rails spec:
|
||||
stage: test
|
||||
needs:
|
||||
- job: build_image
|
||||
tags:
|
||||
- docker
|
||||
needs:
|
||||
|
|
@ -97,7 +100,9 @@ rails spec:
|
|||
expire_in: 30 days
|
||||
|
||||
rails e2e spec:
|
||||
stage: test
|
||||
stage: e2e
|
||||
needs:
|
||||
- job: build_image
|
||||
tags:
|
||||
- docker
|
||||
needs:
|
||||
|
|
@ -151,7 +156,9 @@ rails e2e spec:
|
|||
- inv blackbox-service
|
||||
|
||||
rails follow sync e2e spec:
|
||||
stage: test
|
||||
stage: e2e
|
||||
needs:
|
||||
- job: build_image
|
||||
tags:
|
||||
- docker
|
||||
needs:
|
||||
|
|
|
|||
|
|
@ -39,6 +39,13 @@ class Match < ApplicationRecord
|
|||
finished? ? current_leading_team : nil
|
||||
end
|
||||
|
||||
def loser
|
||||
winning_team = winner
|
||||
return nil if winning_team.nil?
|
||||
|
||||
teams.find { |team| team != winning_team }
|
||||
end
|
||||
|
||||
def group_match?
|
||||
group.present?
|
||||
end
|
||||
|
|
|
|||
|
|
@ -224,12 +224,11 @@ class GroupStageService
|
|||
nil
|
||||
end
|
||||
|
||||
def unresolved_tie_crosses_advancing_cutoff?(sorted_group_scores, group_score, advancing_slots)
|
||||
def unresolved_tie_affects_advancing_placements?(sorted_group_scores, group_score, advancing_slots)
|
||||
return false if advancing_slots.zero?
|
||||
|
||||
cluster = tie_cluster_for(sorted_group_scores, group_score)
|
||||
occupied_slots = (cluster[:start_index] + 1)..(cluster[:end_index] + 1)
|
||||
occupied_slots.begin <= advancing_slots && occupied_slots.end > advancing_slots
|
||||
cluster[:start_index] < advancing_slots
|
||||
end
|
||||
|
||||
def tie_cluster_for(sorted_group_scores, group_score)
|
||||
|
|
@ -298,7 +297,7 @@ class GroupStageService
|
|||
tied: comparison_reason[:tied],
|
||||
needs_decider_match: group_ready_for_decider_check &&
|
||||
comparison_reason[:tied] &&
|
||||
unresolved_tie_crosses_advancing_cutoff?(sorted_group_scores, group_score, advancing_slots)
|
||||
unresolved_tie_affects_advancing_placements?(sorted_group_scores, group_score, advancing_slots)
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ class PlayoffStageService
|
|||
initial_stage.state = :intermediate_stage unless initial_stage.matches.find(&:single_team?).nil?
|
||||
playoffs << initial_stage
|
||||
# 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, teams.size > 3)
|
||||
playoffs.concat empty_stages
|
||||
playoffs
|
||||
end
|
||||
|
|
@ -25,10 +25,12 @@ class PlayoffStageService
|
|||
#
|
||||
# @param stage_count [Integer] number of stages to generate
|
||||
# @return [Array] the generated stages
|
||||
def generate_stages_with_empty_matches(stage_count)
|
||||
def generate_stages_with_empty_matches(stage_count, include_third_place_match = false)
|
||||
empty_stages = []
|
||||
stage_count.times do |i|
|
||||
stage = Stage.new level: i, matches: generate_empty_matches(2**i)
|
||||
match_count = 2**i
|
||||
match_count += 1 if include_third_place_match && i.zero?
|
||||
stage = Stage.new level: i, matches: generate_empty_matches(match_count)
|
||||
empty_stages << stage
|
||||
end
|
||||
# as we are generating the stages in the wrong order (starting with the lowest number of matches (which is
|
||||
|
|
@ -76,33 +78,14 @@ class PlayoffStageService
|
|||
# return if next stage does not exist (there are no matches after the finale)
|
||||
return [] if next_stage.nil?
|
||||
|
||||
current_position = current_match.position
|
||||
|
||||
# a "companion" match is the one that with the selected match makes up the two matches
|
||||
# of which the winners advance into the match below
|
||||
# depending on the position of the match, the companion match is either on the left or right of it
|
||||
companion_match = find_companion_match(current_position, current_stage)
|
||||
|
||||
match_below = next_stage.matches.find { |m| m.position == current_position / 2 }
|
||||
match_scores = match_below.match_scores.sort_by(&:id)
|
||||
companion_match = find_companion_match(current_match.position, current_stage)
|
||||
return populate_final_stage_matches(current_match, companion_match, next_stage) if semifinal_match?(current_stage, next_stage)
|
||||
|
||||
winners = get_winners_of(companion_match, current_match)
|
||||
|
||||
# depending on the amount of match_scores already present we need to do different things
|
||||
match_scores = assign_correct_match_scores!(match_scores, winners)
|
||||
|
||||
# If a match is not decided yet, it will return nil as winner.
|
||||
# This is not allowed in Database. The following code filters out MatchScores that contain nil as team.
|
||||
match_scores = match_scores.select { |ms| ms.team.present? }
|
||||
match_below.match_scores = match_scores
|
||||
match_below.state = if match_below.match_scores.empty? || match_below.match_scores.size == 1
|
||||
:not_ready
|
||||
elsif match_below.match_scores.size == 2
|
||||
:not_started
|
||||
else
|
||||
raise 'Unprocessable amount of match_scores found'
|
||||
end
|
||||
[match_below, match_scores].flatten
|
||||
populate_match!(match: next_stage.matches.find { |m| m.position == current_match.position / 2 }, teams: winners)
|
||||
end
|
||||
|
||||
private
|
||||
|
|
@ -135,9 +118,43 @@ class PlayoffStageService
|
|||
match_scores
|
||||
end
|
||||
|
||||
def populate_match!(match:, teams:)
|
||||
match_scores = match.match_scores.sort_by(&:id)
|
||||
match_scores = assign_correct_match_scores!(match_scores, teams)
|
||||
match_scores = match_scores.select { |ms| ms.team.present? }
|
||||
match.match_scores = match_scores
|
||||
match.state = if match.match_scores.empty? || match.match_scores.size == 1
|
||||
:not_ready
|
||||
elsif match.match_scores.size == 2
|
||||
:not_started
|
||||
else
|
||||
raise 'Unprocessable amount of match_scores found'
|
||||
end
|
||||
[match, match_scores].flatten
|
||||
end
|
||||
|
||||
def get_winners_of(companion_match, current_match)
|
||||
matches = [current_match, companion_match].sort_by(&:position)
|
||||
matches.map(&:winner)
|
||||
end
|
||||
|
||||
def get_losers_of(companion_match, current_match)
|
||||
matches = [current_match, companion_match].sort_by(&:position)
|
||||
matches.map(&:loser)
|
||||
end
|
||||
|
||||
def semifinal_match?(current_stage, next_stage)
|
||||
current_stage.level == 1 && next_stage.level.zero? && next_stage.matches.size > 1
|
||||
end
|
||||
|
||||
def populate_final_stage_matches(current_match, companion_match, next_stage)
|
||||
final_match = next_stage.matches.find { |match| match.position.zero? }
|
||||
third_place_match = next_stage.matches.find { |match| match.position == 1 }
|
||||
|
||||
[
|
||||
populate_match!(match: final_match, teams: get_winners_of(companion_match, current_match)),
|
||||
populate_match!(match: third_place_match, teams: get_losers_of(companion_match, current_match))
|
||||
].flatten
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -64,8 +64,7 @@ module TurniereE2E
|
|||
checkpoints[:before_final_finish_attempt] = summarize_tournament(tournament)
|
||||
return result(session: session, tournament: tournament, checkpoints: checkpoints, stop_at: stop_at) if stop_at == :before_final_finish_attempt
|
||||
|
||||
final_match = final_match_of!(tournament)
|
||||
finish_attempt = play_match!(final_match, winner_name: preferred_playoff_winner_name(final_match))
|
||||
finish_attempt = finish_final_stage_matches!(tournament)
|
||||
tournament = fetch_tournament!(tournament.fetch(:id))
|
||||
checkpoints[:final_finish_attempted] = {
|
||||
finish_attempt: finish_attempt,
|
||||
|
|
@ -97,8 +96,7 @@ module TurniereE2E
|
|||
checkpoints[:before_final_finish_attempt] = summarize_tournament(tournament)
|
||||
return result(session: session, tournament: tournament, checkpoints: checkpoints, stop_at: stop_at) if stop_at == :before_final_finish_attempt
|
||||
|
||||
final_match = final_match_of!(tournament)
|
||||
finish_attempt = play_match!(final_match, winner_name: preferred_playoff_winner_name(final_match))
|
||||
finish_attempt = finish_final_stage_matches!(tournament)
|
||||
tournament = fetch_tournament!(tournament.fetch(:id))
|
||||
checkpoints[:final_finish_attempted] = {
|
||||
finish_attempt: finish_attempt,
|
||||
|
|
@ -340,9 +338,24 @@ module TurniereE2E
|
|||
tournament.fetch(:stages).find { |stage| stage.fetch(:level) == -1 } || raise('group stage not found')
|
||||
end
|
||||
|
||||
def final_match_of!(tournament)
|
||||
def final_stage_matches_of!(tournament)
|
||||
final_stage = tournament.fetch(:stages).find { |stage| stage.fetch(:level) == 0 } || raise('final stage not found')
|
||||
attach_stage_level(final_stage).fetch(:matches).first
|
||||
attach_stage_level(final_stage).fetch(:matches).sort_by { |match| match.fetch(:position) }
|
||||
end
|
||||
|
||||
def final_match_of!(tournament)
|
||||
final_stage_matches_of!(tournament).first
|
||||
end
|
||||
|
||||
def finish_final_stage_matches!(tournament)
|
||||
final_stage_matches_of!(tournament).each do |match|
|
||||
play_match!(match, winner_name: preferred_playoff_winner_name(match))
|
||||
end
|
||||
|
||||
{
|
||||
status: 200,
|
||||
finished_match_ids: final_stage_matches_of!(fetch_tournament!(tournament.fetch(:id))).map { |match| match.fetch(:id) }
|
||||
}
|
||||
end
|
||||
|
||||
def playoff_matches(tournament)
|
||||
|
|
|
|||
|
|
@ -201,5 +201,41 @@ RSpec.describe StagesController, type: :controller do
|
|||
expect(body[:blocking_ties].first[:team_names]).to eq(%w[A B])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when tied advancing teams still need a decider to fix playoff seeding' do
|
||||
let(:group_stage) do
|
||||
create(:group_stage, tournament: create(:tournament, teams_count: 0), group_count: 0)
|
||||
end
|
||||
|
||||
before do
|
||||
tournament = group_stage.tournament
|
||||
tournament.update!(playoff_teams_amount: 2, instant_finalists_amount: 2, intermediate_round_participants_amount: 0)
|
||||
group = create(:group, stage: group_stage, match_count: 0, number: 1)
|
||||
group_stage.groups = [group]
|
||||
team_a = create(:team, tournament: tournament, name: 'A')
|
||||
team_b = create(:team, tournament: tournament, name: 'B')
|
||||
create(:group_match, group: group, state: :finished).tap do |match|
|
||||
create(:match_score, match: match, team: team_a, points: 2)
|
||||
create(:match_score, match: match, team: team_b, points: 2)
|
||||
end
|
||||
score_a = create(:group_score, group: group, team: team_a, group_points: 7, scored_points: 4, received_points: 1)
|
||||
score_b = create(:group_score, group: group, team: team_b, group_points: 7, scored_points: 4, received_points: 1)
|
||||
GroupStageService.send(:recalculate_position_of_group_scores!, [score_a, score_b]).each(&:save!)
|
||||
|
||||
apply_authentication_headers_for group_stage.owner
|
||||
put :update, params: { id: group_stage.to_param }.merge(finished)
|
||||
end
|
||||
|
||||
it 'returns unprocessable entity' do
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
end
|
||||
|
||||
it 'returns a helpful decider error' do
|
||||
body = deserialize_response(response)
|
||||
expect(body[:error]).to eq('Playoffs cannot be generated yet because a decider match is required for an advancing place')
|
||||
expect(body[:blocking_ties].size).to eq(1)
|
||||
expect(body[:blocking_ties].first[:team_names]).to eq(%w[A B])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -50,6 +50,65 @@ RSpec.describe 'Tournament follower sync HTTP E2E' do
|
|||
expect(tournament_signature(follower)).to eq(tournament_signature(source))
|
||||
expect(synced_team_id_pairs(source: source, follower: follower)).not_to be_empty
|
||||
|
||||
log_phase!('swap matches on leader and verify follower behavior')
|
||||
source_group_stage = source.fetch(:stages).find { |stage| stage.fetch(:level) == -1 }
|
||||
first_source_group = source_group_stage.fetch(:groups).sort_by { |group| group.fetch(:number) }.first
|
||||
second_source_group = source_group_stage.fetch(:groups).sort_by { |group| group.fetch(:number) }[1]
|
||||
first_group_matches = first_source_group.fetch(:matches).sort_by { |match| match.fetch(:position) }
|
||||
first_position_match = first_group_matches.fetch(0)
|
||||
fourth_position_match = first_group_matches.fetch(3)
|
||||
wrong_group_match = second_source_group.fetch(:matches).sort_by { |match| match.fetch(:position) }.first
|
||||
follower_group_stage = follower.fetch(:stages).find { |stage| stage.fetch(:level) == -1 }
|
||||
first_follower_group = follower_group_stage.fetch(:groups).sort_by { |group| group.fetch(:number) }.first
|
||||
follower_first_position_match = first_follower_group.fetch(:matches).find do |match|
|
||||
match_team_names(match) == match_team_names(first_position_match)
|
||||
end
|
||||
follower_fourth_position_match = first_follower_group.fetch(:matches).find do |match|
|
||||
match_team_names(match) == match_team_names(fourth_position_match)
|
||||
end
|
||||
|
||||
leader_swap = source_owner_client.patch("/matches/#{first_position_match.fetch(:id)}/swap", body: {
|
||||
other_match_id: fourth_position_match.fetch(:id)
|
||||
})
|
||||
expect(leader_swap[:status]).to eq(200)
|
||||
expect(leader_swap[:json].map { |match| match.fetch(:position) }).to match_array([
|
||||
first_position_match.fetch(:position),
|
||||
fourth_position_match.fetch(:position)
|
||||
])
|
||||
source = fetch_tournament(client: source_anonymous_client, tournament_id: source.fetch(:id))
|
||||
follower = wait_for_tournament_sync!(source_tournament_id: source.fetch(:id), follower_tournament_id: follower.fetch(:id))
|
||||
expect(tournament_signature(follower)).to eq(tournament_signature(source))
|
||||
expect(group_match_positions(source, group_number: first_source_group.fetch(:number))).to eq(
|
||||
group_match_positions(follower, group_number: first_follower_group.fetch(:number))
|
||||
)
|
||||
expect(group_match_positions(source, group_number: first_source_group.fetch(:number)).fetch(match_team_names(first_position_match))).to eq(
|
||||
fourth_position_match.fetch(:position)
|
||||
)
|
||||
expect(group_match_positions(source, group_number: first_source_group.fetch(:number)).fetch(match_team_names(fourth_position_match))).to eq(
|
||||
first_position_match.fetch(:position)
|
||||
)
|
||||
|
||||
follower_before_invalid_swap = tournament_signature(follower)
|
||||
source_before_invalid_swap = tournament_signature(source)
|
||||
|
||||
wrong_group_swap = source_owner_client.patch("/matches/#{first_position_match.fetch(:id)}/swap", body: {
|
||||
other_match_id: wrong_group_match.fetch(:id)
|
||||
})
|
||||
expect(wrong_group_swap[:status]).to eq(422)
|
||||
expect(wrong_group_swap.dig(:json, :error)).to eq('Matches must be different group matches from the same group')
|
||||
source_after_invalid_swap = fetch_tournament(client: source_anonymous_client, tournament_id: source.fetch(:id))
|
||||
follower_after_invalid_swap = fetch_tournament(client: follower_anonymous_client, tournament_id: follower.fetch(:id))
|
||||
expect(tournament_signature(source_after_invalid_swap)).to eq(source_before_invalid_swap)
|
||||
expect(tournament_signature(follower_after_invalid_swap)).to eq(follower_before_invalid_swap)
|
||||
|
||||
follower_swap = follower_owner_client.patch("/matches/#{follower_first_position_match.fetch(:id)}/swap", body: {
|
||||
other_match_id: follower_fourth_position_match.fetch(:id)
|
||||
})
|
||||
expect(follower_swap[:status]).to eq(423)
|
||||
expect(follower_swap.dig(:json, :error)).to eq('Tournament is in read only follower mode')
|
||||
follower_after_locked_swap = fetch_tournament(client: follower_anonymous_client, tournament_id: follower.fetch(:id))
|
||||
expect(tournament_signature(follower_after_locked_swap)).to eq(follower_before_invalid_swap)
|
||||
|
||||
log_phase!('tighten playoff cutoff and rename teams')
|
||||
update_cutoff = source_owner_client.patch("/tournaments/#{source.fetch(:id)}", body: {
|
||||
playoff_teams_amount: 4,
|
||||
|
|
@ -427,6 +486,24 @@ RSpec.describe 'Tournament follower sync HTTP E2E' do
|
|||
}
|
||||
end
|
||||
|
||||
def match_team_names(match)
|
||||
match.fetch(:match_scores)
|
||||
.map { |score| score.dig(:team, :name) }
|
||||
.sort
|
||||
.join(' vs ')
|
||||
end
|
||||
|
||||
def group_match_positions(tournament, group_number:)
|
||||
group = tournament.fetch(:stages)
|
||||
.find { |stage| stage.fetch(:level) == -1 }
|
||||
.fetch(:groups)
|
||||
.find { |candidate| candidate.fetch(:number) == group_number }
|
||||
|
||||
group.fetch(:matches).each_with_object({}) do |match, positions|
|
||||
positions[match_team_names(match)] = match.fetch(:position)
|
||||
end
|
||||
end
|
||||
|
||||
def unique_suffix
|
||||
@unique_suffix_counter ||= 0
|
||||
@unique_suffix_counter += 1
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ RSpec.describe 'Tournament lifecycle HTTP E2E' do
|
|||
final_attempt = result.dig(:checkpoints, :final_finish_attempted, :finish_attempt)
|
||||
expect(final_attempt[:status]).to eq(200)
|
||||
expect(result.dig(:checkpoints, :final_finish_attempted, :tournament, :stage_summaries)
|
||||
.find { |stage| stage[:level] == 0 }[:match_states]).to eq(['finished'])
|
||||
.find { |stage| stage[:level] == 0 }[:match_states]).to eq(%w[finished finished])
|
||||
end
|
||||
|
||||
it 'drives the 3-group intermediate-round path over HTTP through a finished final' do
|
||||
|
|
@ -58,7 +58,7 @@ RSpec.describe 'Tournament lifecycle HTTP E2E' do
|
|||
final_attempt = result.dig(:checkpoints, :final_finish_attempted, :finish_attempt)
|
||||
expect(final_attempt[:status]).to eq(200)
|
||||
expect(result.dig(:checkpoints, :final_finish_attempted, :tournament, :stage_summaries)
|
||||
.find { |stage| stage[:level] == 0 }[:match_states]).to eq(['finished'])
|
||||
.find { |stage| stage[:level] == 0 }[:match_states]).to eq(%w[finished finished])
|
||||
end
|
||||
|
||||
{
|
||||
|
|
@ -126,7 +126,7 @@ RSpec.describe 'Tournament lifecycle HTTP E2E' do
|
|||
|
||||
completed_stages = result.dig(:checkpoints, :completed, :stage_summaries)
|
||||
final_stage = completed_stages.find { |stage| stage[:level] == 0 }
|
||||
expect(final_stage[:match_states]).to eq(['finished'])
|
||||
expect(final_stage[:match_states]).to eq(team_count > 3 ? %w[finished finished] : ['finished'])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -268,7 +268,7 @@ RSpec.describe GroupStageService do
|
|||
)
|
||||
end
|
||||
|
||||
it 'keeps shared positions and requests a decider only when an unresolved tie crosses the cutoff' do
|
||||
it 'keeps shared positions and requests a decider when an unresolved tie affects advancing placements' do
|
||||
tournament.update!(playoff_teams_amount: 1, instant_finalists_amount: 1, intermediate_round_participants_amount: 0)
|
||||
|
||||
create(:group_match, group: group, state: :finished).tap do |match|
|
||||
|
|
@ -300,6 +300,38 @@ RSpec.describe GroupStageService do
|
|||
)
|
||||
end
|
||||
|
||||
it 'requests a decider when tied teams both advance but their seeding stays unresolved' do
|
||||
tournament.update!(playoff_teams_amount: 2, instant_finalists_amount: 2, intermediate_round_participants_amount: 0)
|
||||
|
||||
create(:group_match, group: group, state: :finished).tap do |match|
|
||||
create(:match_score, match: match, team: team_a, points: 2)
|
||||
create(:match_score, match: match, team: team_b, points: 2)
|
||||
end
|
||||
|
||||
score_a = create(:group_score, group: group, team: team_a, group_points: 7, scored_points: 4, received_points: 1)
|
||||
score_b = create(:group_score, group: group, team: team_b, group_points: 7, scored_points: 4, received_points: 1)
|
||||
score_c = create(:group_score, group: group, team: team_c, group_points: 3, scored_points: 2, received_points: 3)
|
||||
score_d = create(:group_score, group: group, team: team_d, group_points: 0, scored_points: 1, received_points: 7)
|
||||
|
||||
GroupStageService.send(:recalculate_position_of_group_scores!, [score_a, score_b, score_c, score_d]).each(&:save!)
|
||||
|
||||
expect([score_a.position, score_b.position, score_c.position, score_d.position]).to eq([1, 1, 3, 4])
|
||||
expect(GroupStageService.ranking_decision_for(score_a)).to include(
|
||||
resolved_by: 'tie',
|
||||
direct_comparison_result: 'draw',
|
||||
hidden_points_result: 'not_played',
|
||||
tied: true,
|
||||
needs_decider_match: true
|
||||
)
|
||||
expect(GroupStageService.ranking_decision_for(score_b)).to include(
|
||||
resolved_by: 'tie',
|
||||
direct_comparison_result: 'draw',
|
||||
hidden_points_result: 'not_played',
|
||||
tied: true,
|
||||
needs_decider_match: true
|
||||
)
|
||||
end
|
||||
|
||||
it 'does not expose unresolved tie metadata while regular matches in the group are still unfinished' do
|
||||
tournament.update!(playoff_teams_amount: 1, instant_finalists_amount: 1, intermediate_round_participants_amount: 0)
|
||||
|
||||
|
|
|
|||
|
|
@ -75,6 +75,13 @@ RSpec.describe PlayoffStageService do
|
|||
expect(stage.level).to eq stage_level
|
||||
end
|
||||
end
|
||||
|
||||
it "generates correct amount of final-stage matches for #{parameters[:team_size]} teams" do
|
||||
teams = build_list(:team, parameters[:team_size])
|
||||
stages = PlayoffStageService.generate_playoff_stages(teams, false)
|
||||
|
||||
expect(stages.find { |stage| stage.level.zero? }.matches.size).to eq(parameters[:team_size] > 3 ? 2 : 1)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'number of teams isn\'t a power of two' do
|
||||
|
|
@ -229,5 +236,42 @@ RSpec.describe PlayoffStageService do
|
|||
expect(PlayoffStageService.populate_match_below(@final_match)).to eq([])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when semifinal finishes in tournament with third-place match' do
|
||||
before do
|
||||
@tournament = create(:stageless_tournament, teams_count: 4)
|
||||
@tournament.stages = PlayoffStageService.generate_playoff_stages(@tournament.teams, false)
|
||||
@tournament.save!
|
||||
semifinal_stage = @tournament.stages.find { |stage| stage.level == 1 }
|
||||
@semifinal_match = semifinal_stage.matches.find { |match| match.position.zero? }
|
||||
@companion_match = semifinal_stage.matches.find { |match| match.position == 1 }
|
||||
|
||||
[@semifinal_match, @companion_match].each do |match|
|
||||
match.update!(state: :finished)
|
||||
match.match_scores.sort_by(&:id).each_with_index do |match_score, index|
|
||||
match_score.update!(points: index.zero? ? 2 : 1)
|
||||
end
|
||||
end
|
||||
|
||||
@result = PlayoffStageService.populate_match_below(@semifinal_match)
|
||||
@final_stage = @tournament.stages.find { |stage| stage.level.zero? }
|
||||
@final_match = @final_stage.matches.find { |match| match.position.zero? }
|
||||
@third_place_match = @final_stage.matches.find { |match| match.position == 1 }
|
||||
end
|
||||
|
||||
it 'fills final with winners' do
|
||||
expect(@final_match.teams).to match_array([@semifinal_match.winner, @companion_match.winner])
|
||||
expect(@final_match.state).to eq('not_started')
|
||||
end
|
||||
|
||||
it 'fills third-place match with losers' do
|
||||
expect(@third_place_match.teams).to match_array([@semifinal_match.loser, @companion_match.loser])
|
||||
expect(@third_place_match.state).to eq('not_started')
|
||||
end
|
||||
|
||||
it 'returns both changed matches' do
|
||||
expect(@result).to include(@final_match, @third_place_match)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ RSpec.describe TournamentSnapshotImporter do
|
|||
expect(sync_signature(follower)).to eq(sync_signature(source))
|
||||
expect(follower.teams.count).to eq(128)
|
||||
expect(follower.stages.where(level: -1).first.groups.count).to eq(32)
|
||||
expect(follower.stages.where('level >= 0').sum { |stage| stage.matches.count }).to eq(63)
|
||||
expect(follower.stages.where('level >= 0').sum { |stage| stage.matches.count }).to eq(64)
|
||||
expect(follower.matches.select(&:decider?).count).to eq(source.matches.select(&:decider?).count)
|
||||
expect(follower.teams.pluck(:sync_source_id)).to match_array(source.teams.pluck(:id))
|
||||
expect(source.teams.pluck(:id) & follower.teams.pluck(:id)).not_to match_array(source.teams.pluck(:id))
|
||||
|
|
|
|||
Loading…
Reference in New Issue