Merge remote-tracking branch 'origin/master' into codex/tur-159-load-tests
This commit is contained in:
commit
1394889087
|
|
@ -3,17 +3,11 @@
|
|||
class TournamentGroupMatchStarter
|
||||
class << self
|
||||
def upcoming_matches(tournament)
|
||||
group_stage = tournament.group_stage
|
||||
return [] if group_stage.nil?
|
||||
|
||||
next_matches = group_stage.groups.filter_map do |group|
|
||||
group.matches.not_started.order(:position).first
|
||||
end
|
||||
lowest_position = next_matches.map(&:position).min
|
||||
group_matches = not_started_group_matches(tournament)
|
||||
lowest_position = group_matches.minimum(:position)
|
||||
return [] if lowest_position.nil?
|
||||
|
||||
next_matches.select { |match| match.position == lowest_position }
|
||||
.sort_by { |match| [match.position, match.group.number, match.id] }
|
||||
selected_lowest_matches(group_matches, lowest_position)
|
||||
end
|
||||
|
||||
def start_next!(tournament, logger: Rails.logger)
|
||||
|
|
@ -30,6 +24,25 @@ class TournamentGroupMatchStarter
|
|||
|
||||
private
|
||||
|
||||
def not_started_group_matches(tournament)
|
||||
group_stage = tournament.group_stage
|
||||
return Match.none if group_stage.nil?
|
||||
|
||||
group_ids = group_stage.groups.ids
|
||||
return Match.none if group_ids.empty?
|
||||
|
||||
Match.where(group_id: group_ids).not_started
|
||||
end
|
||||
|
||||
def selected_lowest_matches(group_matches, lowest_position)
|
||||
group_matches.where(position: lowest_position)
|
||||
.includes(:group)
|
||||
.group_by(&:group_id)
|
||||
.values
|
||||
.map { |matches| matches.min_by(&:id) }
|
||||
.sort_by { |match| [match.position, match.group.number, match.id] }
|
||||
end
|
||||
|
||||
def recalculate_group_scores!(matches, logger:)
|
||||
matches.map(&:group).uniq.each do |group|
|
||||
next if UpdateGroupsGroupScoresAndSave.call(group:).success?
|
||||
|
|
|
|||
|
|
@ -412,43 +412,54 @@ RSpec.describe 'Backend API surface HTTP E2E' do
|
|||
.to eq([first_match.fetch(:position)])
|
||||
end
|
||||
|
||||
it 'starts next group matches at the lowest pending position and returns started matches' do
|
||||
it 'starts only a single lagging group before all groups can advance over HTTP' do
|
||||
tournament = create_group_stage_tournament(client: owner_client, public: false, name_prefix: 'Start Next Groups')
|
||||
groups = tournament.fetch(:stages).find { |stage| stage.fetch(:level) == -1 }
|
||||
.fetch(:groups)
|
||||
.sort_by { |group| group.fetch(:number) }
|
||||
matches_by_group = groups.to_h do |group|
|
||||
[group.fetch(:id), group.fetch(:matches).sort_by { |match| match.fetch(:position) }]
|
||||
end
|
||||
groups = group_stage_groups(tournament)
|
||||
matches_by_group = indexed_group_matches(groups)
|
||||
start_group_positions!(matches_by_group:, group: groups[0], positions: [0, 1])
|
||||
start_group_positions!(matches_by_group:, group: groups[1], positions: [0])
|
||||
start_group_positions!(matches_by_group:, group: groups[2], positions: [0, 1])
|
||||
start_group_positions!(matches_by_group:, group: groups[3], positions: [0, 1])
|
||||
|
||||
first_start = owner_client.patch("/tournaments/#{tournament.fetch(:id)}/start_next_group_matches")
|
||||
expect(first_start[:status]).to eq(200)
|
||||
expect(first_start[:json].map { |match| match.dig(:group, :id) })
|
||||
.to match_array(groups.map { |group| group.fetch(:id) })
|
||||
expect(first_start[:json].map { |match| match.fetch(:position) }.uniq).to eq([0])
|
||||
|
||||
matches_by_group.fetch(groups.first.fetch(:id))[1..3].each do |match|
|
||||
start_group_match!(match_id: match.fetch(:id))
|
||||
end
|
||||
groups.drop(1).each do |group|
|
||||
matches_by_group.fetch(group.fetch(:id))[1..2].each do |match|
|
||||
start_group_match!(match_id: match.fetch(:id))
|
||||
end
|
||||
end
|
||||
|
||||
expected_match_ids = groups.drop(1).map do |group|
|
||||
matches_by_group.fetch(group.fetch(:id)).fetch(3).fetch(:id)
|
||||
end
|
||||
first_expected_ids = match_ids_for_positions(matches_by_group:, pairs: [[groups[1], 1]])
|
||||
second_expected_ids = match_ids_for_positions(
|
||||
matches_by_group:,
|
||||
pairs: groups.map { |group| [group, 2] }
|
||||
)
|
||||
upcoming = owner_client.get("/tournaments/#{tournament.fetch(:id)}/matches", params: { state: 'upcoming' })
|
||||
catch_up_start = owner_client.patch("/tournaments/#{tournament.fetch(:id)}/start_next_group_matches")
|
||||
first_start = owner_client.patch("/tournaments/#{tournament.fetch(:id)}/start_next_group_matches")
|
||||
second_start = owner_client.patch("/tournaments/#{tournament.fetch(:id)}/start_next_group_matches")
|
||||
|
||||
expect(upcoming[:status]).to eq(200)
|
||||
expect(upcoming[:json].map { |match| match.fetch(:id) }).to match_array(expected_match_ids)
|
||||
expect_match_ids(upcoming, first_expected_ids)
|
||||
expect_started_match_ids(first_start, first_expected_ids)
|
||||
expect_started_match_ids(second_start, second_expected_ids)
|
||||
end
|
||||
|
||||
expect(catch_up_start[:status]).to eq(200)
|
||||
expect(catch_up_start[:json].map { |match| match.fetch(:id) }).to match_array(expected_match_ids)
|
||||
expect(catch_up_start[:json].map { |match| match.fetch(:position) }.uniq).to eq([3])
|
||||
expect(catch_up_start[:json].map { |match| match.fetch(:state) }.uniq).to eq(['in_progress'])
|
||||
it 'starts catch-up waves before the next position is started everywhere over HTTP' do
|
||||
tournament = create_group_stage_tournament(client: owner_client, public: false, name_prefix: 'Start Catch Up Waves')
|
||||
groups = group_stage_groups(tournament)
|
||||
matches_by_group = indexed_group_matches(groups)
|
||||
start_group_positions!(matches_by_group:, group: groups[0], positions: [0, 1, 2])
|
||||
start_group_positions!(matches_by_group:, group: groups[1], positions: [0])
|
||||
start_group_positions!(matches_by_group:, group: groups[2], positions: [0, 1, 2])
|
||||
start_group_positions!(matches_by_group:, group: groups[3], positions: [0, 1])
|
||||
|
||||
first_expected_ids = match_ids_for_positions(matches_by_group:, pairs: [[groups[1], 1]])
|
||||
second_expected_ids = match_ids_for_positions(
|
||||
matches_by_group:,
|
||||
pairs: [[groups[1], 2], [groups[3], 2]]
|
||||
)
|
||||
third_expected_ids = match_ids_for_positions(
|
||||
matches_by_group:,
|
||||
pairs: groups.map { |group| [group, 3] }
|
||||
)
|
||||
first_start = owner_client.patch("/tournaments/#{tournament.fetch(:id)}/start_next_group_matches")
|
||||
second_start = owner_client.patch("/tournaments/#{tournament.fetch(:id)}/start_next_group_matches")
|
||||
third_start = owner_client.patch("/tournaments/#{tournament.fetch(:id)}/start_next_group_matches")
|
||||
|
||||
expect_started_match_ids(first_start, first_expected_ids)
|
||||
expect_started_match_ids(second_start, second_expected_ids)
|
||||
expect_started_match_ids(third_start, third_expected_ids)
|
||||
end
|
||||
|
||||
def login_client(email:, password:)
|
||||
|
|
@ -505,6 +516,40 @@ RSpec.describe 'Backend API surface HTTP E2E' do
|
|||
fetch_tournament(client: client, tournament_id: response.dig(:json, :id))
|
||||
end
|
||||
|
||||
def group_stage_groups(tournament)
|
||||
tournament.fetch(:stages).find { |stage| stage.fetch(:level) == -1 }
|
||||
.fetch(:groups)
|
||||
.sort_by { |group| group.fetch(:number) }
|
||||
end
|
||||
|
||||
def indexed_group_matches(groups)
|
||||
groups.to_h do |group|
|
||||
[group.fetch(:id), group.fetch(:matches).sort_by { |match| match.fetch(:position) }]
|
||||
end
|
||||
end
|
||||
|
||||
def start_group_positions!(matches_by_group:, group:, positions:)
|
||||
positions.each do |position|
|
||||
start_group_match!(match_id: matches_by_group.fetch(group.fetch(:id)).fetch(position).fetch(:id))
|
||||
end
|
||||
end
|
||||
|
||||
def match_ids_for_positions(matches_by_group:, pairs:)
|
||||
pairs.map do |group, position|
|
||||
matches_by_group.fetch(group.fetch(:id)).fetch(position).fetch(:id)
|
||||
end
|
||||
end
|
||||
|
||||
def expect_match_ids(response, expected_ids)
|
||||
expect(response[:status]).to eq(200)
|
||||
expect(response[:json].map { |match| match.fetch(:id) }).to match_array(expected_ids)
|
||||
end
|
||||
|
||||
def expect_started_match_ids(response, expected_ids)
|
||||
expect_match_ids(response, expected_ids)
|
||||
expect(response[:json].map { |match| match.fetch(:state) }.uniq).to eq(['in_progress'])
|
||||
end
|
||||
|
||||
def create_playoff_tournament(client:, public:, name_prefix:)
|
||||
payload = {
|
||||
name: "#{name_prefix} #{unique_suffix}",
|
||||
|
|
|
|||
|
|
@ -7,29 +7,50 @@ RSpec.describe TournamentGroupMatchStarter do
|
|||
let(:groups) { tournament.group_stage.groups.sort_by(&:number) }
|
||||
|
||||
before do
|
||||
set_group_match_states(groups[0], %i[finished finished finished in_progress])
|
||||
groups.drop(1).each do |group|
|
||||
set_group_match_states(group, %i[finished finished in_progress not_started])
|
||||
end
|
||||
groups.each { |group| set_group_match_states(group, Array.new(4, :not_started)) }
|
||||
end
|
||||
|
||||
describe '.upcoming_matches' do
|
||||
it 'returns only groups whose next unstarted match is at the lowest pending position' do
|
||||
start_group_positions(groups[0], [0, 1, 2])
|
||||
start_group_positions(groups[1], [0, 1])
|
||||
start_group_positions(groups[2], [0, 1])
|
||||
start_group_positions(groups[3], [0, 1])
|
||||
|
||||
matches = described_class.upcoming_matches(tournament)
|
||||
|
||||
expect(matches.map(&:group)).to match_array(groups.drop(1))
|
||||
expect(matches.map(&:position).uniq).to eq([3])
|
||||
expect(matches.map(&:position).uniq).to eq([2])
|
||||
end
|
||||
end
|
||||
|
||||
describe '.start_next!' do
|
||||
it 'starts selected catch-up matches and returns them' do
|
||||
started_matches = described_class.start_next!(tournament, logger: Logger.new(nil))
|
||||
it 'starts only a single lagging group before all groups can advance' do
|
||||
start_group_positions(groups[0], [0, 1])
|
||||
start_group_positions(groups[1], [0])
|
||||
start_group_positions(groups[2], [0, 1])
|
||||
start_group_positions(groups[3], [0, 1])
|
||||
|
||||
expect(started_matches.map(&:group)).to match_array(groups.drop(1))
|
||||
expect(started_matches.map(&:position).uniq).to eq([3])
|
||||
expect(started_matches.map { |match| match.reload.state }.uniq).to eq(['in_progress'])
|
||||
expect(groups.first.matches.find_by!(position: 3).reload).to be_in_progress
|
||||
first_start = described_class.start_next!(tournament, logger: Logger.new(nil))
|
||||
second_start = described_class.start_next!(tournament, logger: Logger.new(nil))
|
||||
|
||||
expect_group_positions(first_start, [[groups[1], 1]])
|
||||
expect_group_positions(second_start, groups.map { |group| [group, 2] })
|
||||
end
|
||||
|
||||
it 'starts catch-up waves before the next position is started everywhere' do
|
||||
start_group_positions(groups[0], [0, 1, 2])
|
||||
start_group_positions(groups[1], [0])
|
||||
start_group_positions(groups[2], [0, 1, 2])
|
||||
start_group_positions(groups[3], [0, 1])
|
||||
|
||||
first_start = described_class.start_next!(tournament, logger: Logger.new(nil))
|
||||
second_start = described_class.start_next!(tournament, logger: Logger.new(nil))
|
||||
third_start = described_class.start_next!(tournament, logger: Logger.new(nil))
|
||||
|
||||
expect_group_positions(first_start, [[groups[1], 1]])
|
||||
expect_group_positions(second_start, [[groups[1], 2], [groups[3], 2]])
|
||||
expect_group_positions(third_start, groups.map { |group| [group, 3] })
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -38,4 +59,15 @@ RSpec.describe TournamentGroupMatchStarter do
|
|||
match.update!(position: index, state: states.fetch(index))
|
||||
end
|
||||
end
|
||||
|
||||
def start_group_positions(group, positions)
|
||||
positions.each do |position|
|
||||
group.matches.find_by!(position:).update!(state: :in_progress)
|
||||
end
|
||||
end
|
||||
|
||||
def expect_group_positions(matches, expected_pairs)
|
||||
expect(matches.map { |match| [match.group, match.position] }).to match_array(expected_pairs)
|
||||
expect(matches.map { |match| match.reload.state }.uniq).to eq(['in_progress'])
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in New Issue