diff --git a/.travis.yml b/.travis.yml index 09c5056..d413499 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,9 +5,9 @@ addons: env: - RAILS_ENV=test script: - - sonar-scanner - bundle exec rails db:migrate - bundle exec rails spec + - sonar-scanner notifications: slack: secure: EDlQKAXSltE2d4vdOtwVdFhPScjFU2rsSSgAGSqV24br4Jb/KihpjracavZW5wnmloiWe0ulj19j7LOtJSCNJOGqeAnct+axNyBRTI+9ctpeBDMHHtiOH9IX2EBsnEBpHdL4gMgOrPFfMoyn+sqbZ7EJgOFU41f/c7X0XUf1QeJ02Gh/uY1+m8Qo0eT9x4u8W+wnCFYCQeTWOB9/4aemkgbELOEDCbLYr5n+HCGK1vi+glmYoyldVr2yQBnbfME2fcNSOb7ytPDzjBI00cdGVhj8e/AMsF84W+Q+U3RIF0zjestQeFp3lPtTcHDt/MRH39MV1fjRaZB4A8+QYrjuECJ6wjzvzXJbGWUjE++6OmbRmszPlkFxXDiiiAe/Vs1NzUr4i7c2aWZhq8Q/6HDwYXx+/OUJY3THpCHjel/PC49s+KZqMrmq53nd6NWSCtZSPCXN/1uqb3m/zUq7i4wSNFirN+9E8reYkEq6GrpG1VwZkpKp9SkjWnd88cgM0JQEpC/dxRrmeI3o+uPRSIXV+RIaGCXIAdWO7eWBIJdpVQNrA4GDjWc+zj0X02qgbn6d6iByFCDtXzB+ognZwmKUnpJ4tF3oh5xv7j6cFw/GNirgThTLwEoXMfC/Q9OmhlYByOsZ+PBApsj0hfs74YXfN753eCglmtOKGqkpRT6kwG8= diff --git a/app/controllers/matches_controller.rb b/app/controllers/matches_controller.rb index c65b521..d64c60d 100644 --- a/app/controllers/matches_controller.rb +++ b/app/controllers/matches_controller.rb @@ -13,12 +13,14 @@ class MatchesController < ApplicationController # PATCH/PUT /matches/1 def update new_state = match_params['state'] - if new_state == 'finished' - # implement logic to move the winning team into the next stage - match_params['state'] = 'team1_won' # or 'team2_won' or 'undecided' - render json: {}, status: :not_implemented - end if @match.update(match_params) + if new_state == 'finished' + result = PopulateMatchBelowAndSave.call(match: @match) unless @match.group_match? + unless result.success? + render json: { error: 'Moving Team one stage down failed' }, status: :unprocessable_entity + return + end + end render json: @match else render json: @match.errors, status: :unprocessable_entity diff --git a/app/controllers/tournaments_controller.rb b/app/controllers/tournaments_controller.rb index 6a4a3e5..6f80705 100644 --- a/app/controllers/tournaments_controller.rb +++ b/app/controllers/tournaments_controller.rb @@ -38,14 +38,14 @@ class TournamentsController < ApplicationController if group_stage groups = organize_teams_in_groups(teams) # add groups to tournament - result = AddGroupStageToTournamentAndSaveTournamentToDatabase.call(tournament: tournament, groups: groups) + result = AddGroupStageToTournamentAndSave.call(tournament: tournament, groups: groups) else # convert teams parameter into Team objects teams = teams.map(&method(:find_or_create_team)) # associate provided teams with tournament tournament.teams = teams # add playoff stage to tournament - result = AddPlayoffsToTournamentAndSaveTournamentToDatabase.call(tournament: tournament) + result = AddPlayoffsToTournamentAndSave.call(tournament: tournament) end # validate tournament unless tournament.valid? diff --git a/app/interactors/add_group_stage_to_tournament.rb b/app/interactors/add_group_stage_to_tournament.rb index 14b798c..d1d28e2 100644 --- a/app/interactors/add_group_stage_to_tournament.rb +++ b/app/interactors/add_group_stage_to_tournament.rb @@ -10,7 +10,7 @@ class AddGroupStageToTournament begin group_stage = GroupStageService.generate_group_stage(groups) tournament.stages = [group_stage] - context.tournament = tournament + context.object_to_save = tournament rescue StandardError context.fail! end diff --git a/app/interactors/add_playoffs_to_tournament.rb b/app/interactors/add_playoffs_to_tournament.rb index fbe7f25..9efc2ec 100644 --- a/app/interactors/add_playoffs_to_tournament.rb +++ b/app/interactors/add_playoffs_to_tournament.rb @@ -12,7 +12,7 @@ class AddPlayoffsToTournament else tournament.stages.concat playoff_stages end - context.tournament = tournament + context.object_to_save = tournament else context.fail! end diff --git a/app/interactors/populate_match_below.rb b/app/interactors/populate_match_below.rb new file mode 100644 index 0000000..96a58c3 --- /dev/null +++ b/app/interactors/populate_match_below.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class PopulateMatchBelow + include Interactor + + def call + match = context.match + begin + objects_to_save = PlayoffStageService.populate_match_below(match) + context.object_to_save = objects_to_save + rescue StandardError + context.fail! + end + end +end diff --git a/app/interactors/save_application_record_object.rb b/app/interactors/save_application_record_object.rb new file mode 100644 index 0000000..9239a78 --- /dev/null +++ b/app/interactors/save_application_record_object.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class SaveApplicationRecordObject + include Interactor + + def call + Array(context.object_to_save).flatten.each do |object| + context.fail! unless object.save + end + end +end diff --git a/app/interactors/save_tournament_to_database.rb b/app/interactors/save_tournament_to_database.rb deleted file mode 100644 index 40acf12..0000000 --- a/app/interactors/save_tournament_to_database.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -class SaveTournamentToDatabase - include Interactor - - def call - if context.tournament.save - nil - else - context.fail! - end - end -end diff --git a/app/models/match.rb b/app/models/match.rb index 8ab9756..2779fa7 100644 --- a/app/models/match.rb +++ b/app/models/match.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class Match < ApplicationRecord - enum state: %i[single_team not_ready not_started in_progress team1_won team2_won undecided] + enum state: %i[single_team not_ready not_started in_progress finished undecided] belongs_to :stage, optional: true belongs_to :group, optional: true @@ -19,23 +19,20 @@ class Match < ApplicationRecord stage ? stage.owner : group.owner end - private + def winner + return nil unless finished? + return nil if match_scores.first.points == match_scores.second.points - def stage_xor_group - errors.add(:stage_xor_group, 'Stage and Group missing or both present') unless stage.present? ^ group.present? - end - - def evaluate_status - if score_team1 < score_team2 - :team2_won - elsif score_team2 < score_team1 - :team1_won - else - group_match? ? :undecided : :in_progress - end + match_scores.max_by(&:points).team end def group_match? group.present? end + + private + + def stage_xor_group + errors.add(:stage_xor_group, 'Stage and Group missing or both present') unless stage.present? ^ group.present? + end end diff --git a/app/organizers/add_group_stage_to_tournament_and_save.rb b/app/organizers/add_group_stage_to_tournament_and_save.rb new file mode 100644 index 0000000..b1f1012 --- /dev/null +++ b/app/organizers/add_group_stage_to_tournament_and_save.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddGroupStageToTournamentAndSave + include Interactor::Organizer + + organize AddGroupStageToTournament, SaveApplicationRecordObject +end diff --git a/app/organizers/add_group_stage_to_tournament_and_save_tournament_to_database.rb b/app/organizers/add_group_stage_to_tournament_and_save_tournament_to_database.rb deleted file mode 100644 index 83710b5..0000000 --- a/app/organizers/add_group_stage_to_tournament_and_save_tournament_to_database.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -class AddGroupStageToTournamentAndSaveTournamentToDatabase - include Interactor::Organizer - - organize AddGroupStageToTournament, SaveTournamentToDatabase -end diff --git a/app/organizers/add_playoffs_to_tournament_and_save.rb b/app/organizers/add_playoffs_to_tournament_and_save.rb new file mode 100644 index 0000000..766add6 --- /dev/null +++ b/app/organizers/add_playoffs_to_tournament_and_save.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddPlayoffsToTournamentAndSave + include Interactor::Organizer + + organize AddPlayoffsToTournament, SaveApplicationRecordObject +end diff --git a/app/organizers/add_playoffs_to_tournament_and_save_tournament_to_database.rb b/app/organizers/add_playoffs_to_tournament_and_save_tournament_to_database.rb deleted file mode 100644 index d4466a0..0000000 --- a/app/organizers/add_playoffs_to_tournament_and_save_tournament_to_database.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -class AddPlayoffsToTournamentAndSaveTournamentToDatabase - include Interactor::Organizer - - organize AddPlayoffsToTournament, SaveTournamentToDatabase -end diff --git a/app/organizers/populate_match_below_and_save.rb b/app/organizers/populate_match_below_and_save.rb new file mode 100644 index 0000000..e947b90 --- /dev/null +++ b/app/organizers/populate_match_below_and_save.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class PopulateMatchBelowAndSave + include Interactor::Organizer + + organize PopulateMatchBelow, SaveApplicationRecordObject +end diff --git a/app/services/playoff_stage_service.rb b/app/services/playoff_stage_service.rb index 340a9a2..38ccabc 100644 --- a/app/services/playoff_stage_service.rb +++ b/app/services/playoff_stage_service.rb @@ -1,73 +1,149 @@ # frozen_string_literal: true class PlayoffStageService - # Generates the playoff stage given the tournament - # - # @param teams [Array] The teams to generate the playoff stages with - # @return [Array] the generated playoff stages - def self.generate_playoff_stages(teams) - playoffs = [] - stage_count = calculate_required_stage_count(teams.size) - # 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_stage = Stage.new level: stage_count - 1, matches: initial_matches - 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.each do |stage| - playoffs << stage + class << self + # Generates the playoff stage given the tournament + # + # @param teams [Array] The teams to generate the playoff stages with + # @return [Array] the generated playoff stages + def generate_playoff_stages(teams) + playoffs = [] + stage_count = calculate_required_stage_count(teams.size) + # 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_stage = Stage.new level: stage_count - 1, matches: initial_matches + 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.each do |stage| + playoffs << stage + end + playoffs end - playoffs - end - # Generates the playoff stage given the tournament - # - # @param tournament [Tournament] The tournament to generate the playoff stages from - # @return [Array] the generated playoff stages - def self.generate_playoff_stages_from_tournament(tournament) - generate_playoff_stages tournament.teams - end - - # Generates given number of empty stages - # - # @param stage_count [Integer] number of stages to generate - # @return [Array] the generated stages - def self.generate_stages_with_empty_matches(stage_count) - empty_stages = [] - stage_count.times do |i| - stage = Stage.new level: i, matches: generate_empty_matches(2**i) - empty_stages << stage + # Generates the playoff stage given the tournament + # + # @param tournament [Tournament] The tournament to generate the playoff stages from + # @return [Array] the generated playoff stages + def generate_playoff_stages_from_tournament(tournament) + generate_playoff_stages tournament.teams end - # as we are generating the stages in the wrong order (starting with the lowest number of matches (which is - # the final stage)) they need to be reversed - empty_stages.reverse! - end - # Generates a number of empty matches to fill later stages - # - # @param amount [Integer] the amount of matches to generate - # @return [Array] the generated matches - def self.generate_empty_matches(amount) - matches = [] - amount.times do |i| - match = Match.new state: :not_ready, position: i - matches << match + # Generates given number of empty stages + # + # @param stage_count [Integer] number of stages to generate + # @return [Array] the generated stages + def generate_stages_with_empty_matches(stage_count) + empty_stages = [] + stage_count.times do |i| + stage = Stage.new level: i, matches: generate_empty_matches(2**i) + empty_stages << stage + end + # as we are generating the stages in the wrong order (starting with the lowest number of matches (which is + # the final stage)) they need to be reversed + empty_stages.reverse! end - matches - end - # Calculates how many stages are required for given number of teams - # - # @param number_of_teams [Integer] the teams number of teams to calculate amount of stages - # @return [Integer] amount of required stages - def self.calculate_required_stage_count(number_of_teams) - if number_of_teams == 1 - 1 - else - # black voodoo magic - stage_count = Math.log(Utils.next_power_of_two(number_of_teams)) / Math.log(2) - stage_count -= 1 if Utils.po2?(number_of_teams) - stage_count.to_int + # Generates a number of empty matches to fill later stages + # + # @param amount [Integer] the amount of matches to generate + # @return [Array] the generated matches + def generate_empty_matches(amount) + matches = [] + amount.times do |i| + match = Match.new state: :not_ready, position: i + matches << match + end + matches + end + + # Calculates how many stages are required for given number of teams + # + # @param number_of_teams [Integer] the teams number of teams to calculate amount of stages + # @return [Integer] amount of required stages + def calculate_required_stage_count(number_of_teams) + if number_of_teams == 1 + 1 + else + # black voodoo magic + stage_count = Math.log(Utils.next_power_of_two(number_of_teams)) / Math.log(2) + stage_count -= 1 if Utils.po2?(number_of_teams) + stage_count.to_int + end + end + + # Populates the match below given match with the winners of the matches above + # + # @param current_match [Match] The Match which finished, the match below it gets populated + # @return [Array] the objects that changed and need to be saved + def populate_match_below(current_match) + current_stage = current_match.stage + next_stage = current_stage.tournament.stages.find { |s| s.level == current_stage.level - 1 } + # 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) + + 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 + end + + private + + def find_companion_match(current_position, current_stage) + companion_match_position = current_position.even? ? current_position + 1 : current_position - 1 + current_stage.matches.find { |m| m.position == companion_match_position } + end + + def assign_correct_match_scores!(match_scores, winners) + case match_scores.size + when 0 + # when 0 match_scores are already there we create both of them with the respective winner from above + match_scores = winners.map { |winner| MatchScore.new(team: winner) } + when 1 + # when 1 match_score is present, we need to check which team is contained within and add the other team as well + if match_scores.first.team == winners.first + match_scores.push MatchScore.new(team: winners.second) + elsif match_scores.first.team == winners.second + match_scores.push MatchScore.new(team: winners.first) + else + match_scores.first.destroy + match_scores = winners.map { |winner| MatchScore.new(team: winner) } + end + when 2 + # when 2 match_scores are present, the teams just get overwritten + match_scores.first.team = winners.first + match_scores.second.team = winners.second + end + match_scores + end + + def get_winners_of(companion_match, current_match) + matches = [current_match, companion_match].sort_by(&:position) + matches.map(&:winner) end end end diff --git a/spec/controllers/matches_controller_spec.rb b/spec/controllers/matches_controller_spec.rb index 10b0a30..7606be1 100644 --- a/spec/controllers/matches_controller_spec.rb +++ b/spec/controllers/matches_controller_spec.rb @@ -5,6 +5,9 @@ require 'rails_helper' RSpec.describe MatchesController, type: :controller do before do @match = create(:match, state: :not_started) + @amount_of_stages = 2 + @tournament = create(:stage_tournament, stage_count: @amount_of_stages) + @running_playoff_match = @tournament.stages.find_by(level: @amount_of_stages).matches.first @match.match_scores = create_pair(:match_score) end @@ -32,7 +35,7 @@ RSpec.describe MatchesController, type: :controller do let(:invalid_update) do { - state: 'team1_won' + state: 'finished' } end @@ -55,6 +58,48 @@ RSpec.describe MatchesController, type: :controller do body = deserialize_response response expect(body[:state]).to eq(valid_update[:state]) end + + context 'on a running playoff match' do + let(:finished) do + { + state: 'finished' + } + end + + before(:each) do + apply_authentication_headers_for @running_playoff_match.owner + end + + before do + @running_playoff_match.match_scores.each_with_index do |ms, i| + ms.points = i + ms.save! + end + put :update, params: { id: @running_playoff_match.to_param }.merge(finished) + @running_playoff_match.reload + end + + it 'updates the matches status' do + expect(response).to be_successful + expect(@running_playoff_match.state).to eq(finished[:state]) + end + + describe 'updates the match below' do + before do + @match_below = @tournament.stages.find_by(level: @amount_of_stages - 1).matches + .find_by(position: @running_playoff_match.position / 2).reload + end + + it 'with the right teams' do + expect(@running_playoff_match.winner).to be_a(Team) + expect(@match_below.teams).to include(@running_playoff_match.winner) + end + + it 'with the right status' do + expect(@match_below.state).to eq('not_ready') + end + end + end end context 'with invalid params' do diff --git a/spec/factories/matches.rb b/spec/factories/matches.rb index 030c87c..43fb55d 100644 --- a/spec/factories/matches.rb +++ b/spec/factories/matches.rb @@ -27,7 +27,7 @@ FactoryBot.define do # random number generated by blapplications match.match_scores.first.points += 1 end - state { :team1_won } + state { :finished } end end @@ -51,7 +51,7 @@ FactoryBot.define do after(:create) do |match, evaluator| match.match_scores = create_list(:match_score, evaluator.match_scores_count, points: 3) end - state { :team1_won } + state { :finished } end end end diff --git a/spec/factories/tournaments.rb b/spec/factories/tournaments.rb index 6d7098e..637564c 100644 --- a/spec/factories/tournaments.rb +++ b/spec/factories/tournaments.rb @@ -32,8 +32,14 @@ FactoryBot.define do :playoff_stage, level: level, match_count: -1, - match_type: evaluator.stage_count ? :running_playoff_match : :empty_prepared_playoff_match + match_type: level == evaluator.stage_count ? :running_playoff_match : :empty_prepared_playoff_match ) + tournament.stages.each do |stage| + stage.matches.each_with_index do |match, i| + match.position = i + match.save! + end + 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 b64c752..d7a3208 100644 --- a/spec/interactors/add_group_stage_to_tournament_interactor_spec.rb +++ b/spec/interactors/add_group_stage_to_tournament_interactor_spec.rb @@ -10,8 +10,8 @@ RSpec.describe AddGroupStageToTournament do end before do - @empty_tournament = create(:stage_tournament, stage_count: 0) - @group_stage_tournament = create(:group_stage_tournament) + @empty_tournament = create(:stageless_tournament) + @group_stage_tournament = create(:group_stage_only_tournament, group_count: 0) @group_stage = create(:group_stage) @groups = Hash[1 => create_list(:team, 4), 2 => create_list(:team, 4)].values end diff --git a/spec/interactors/add_playoffs_to_tournament_interactor_spec.rb b/spec/interactors/add_playoffs_to_tournament_interactor_spec.rb index 056dc2b..884f0e8 100644 --- a/spec/interactors/add_playoffs_to_tournament_interactor_spec.rb +++ b/spec/interactors/add_playoffs_to_tournament_interactor_spec.rb @@ -14,10 +14,10 @@ RSpec.describe AddPlayoffsToTournament do end before do - @group_stage_tournament = create(:stage_tournament) - @playoff_stage_tournament = create(:tournament) - @full_tournament = create(:stage_tournament, stage_count: 5) - @stages = create_list(:stage, 5) + @group_stage_tournament = create(:group_stage_only_tournament, group_count: 0) + @playoff_stage_tournament = create(:stageless_tournament) + @full_tournament = create(:dummy_stage_tournament) + @stages = create_list(:stage, 3) end context 'PlayoffStageService mocked' do diff --git a/spec/interactors/populate_match_below_interactor_spec.rb b/spec/interactors/populate_match_below_interactor_spec.rb new file mode 100644 index 0000000..8b751e0 --- /dev/null +++ b/spec/interactors/populate_match_below_interactor_spec.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +RSpec.describe PopulateMatchBelow do + before do + @match = create(:match) + @objects_to_save = [create(:match), create_list(:match_score, 2)] + end + + context 'no exception' do + let(:context) do + PopulateMatchBelow.call(match: @match) + end + before do + allow(PlayoffStageService) + .to receive(:populate_match_below).with(@match) + .and_return(@objects_to_save) + end + + it 'succeeds' do + expect(context).to be_a_success + end + + it 'provides the objects to save' do + expect(context.object_to_save).to match_array(@objects_to_save) + end + end + + context 'exception is thrown' do + let(:context) do + PopulateMatchBelow.call(match: @match) + end + before do + allow(PlayoffStageService) + .to receive(:populate_match_below).with(@match) + .and_throw('This failed :(') + end + + it 'fails' do + test = context.failure? + expect(test).to eq(true) + end + end +end diff --git a/spec/interactors/save_tournament_to_database_interactor_spec.rb b/spec/interactors/save_application_record_object_to_database_interactor_spec.rb similarity index 72% rename from spec/interactors/save_tournament_to_database_interactor_spec.rb rename to spec/interactors/save_application_record_object_to_database_interactor_spec.rb index b10bba5..ce2edb3 100644 --- a/spec/interactors/save_tournament_to_database_interactor_spec.rb +++ b/spec/interactors/save_application_record_object_to_database_interactor_spec.rb @@ -1,13 +1,13 @@ # frozen_string_literal: true -RSpec.describe SaveTournamentToDatabase do +RSpec.describe SaveApplicationRecordObject do before do @tournament = create(:tournament) end context 'save succeeds' do let(:context) do - SaveTournamentToDatabase.call(tournament: @tournament) + SaveApplicationRecordObject.call(object_to_save: @tournament) end before do expect_any_instance_of(Tournament) @@ -19,13 +19,13 @@ RSpec.describe SaveTournamentToDatabase do end it 'provides the tournament' do - expect(context.tournament).to eq(@tournament) + expect(context.object_to_save).to eq(@tournament) end end context 'save fails' do let(:context) do - SaveTournamentToDatabase.call(tournament: @tournament) + SaveApplicationRecordObject.call(object_to_save: @tournament) end before do expect_any_instance_of(Tournament) diff --git a/spec/models/match_spec.rb b/spec/models/match_spec.rb index 5acec23..0d4656c 100644 --- a/spec/models/match_spec.rb +++ b/spec/models/match_spec.rb @@ -43,6 +43,20 @@ RSpec.describe Match, type: :model do end end + context '#winner' do + it 'returns a winner Team for a decided match' do + decided_playoff_match = create(:decided_playoff_match) + winning_team_match_score = decided_playoff_match.match_scores.first + winning_team_match_score.points = 9999 + winning_team = winning_team_match_score.team + expect(decided_playoff_match.winner).to be winning_team + end + + it 'returns nil for an undecided match' do + expect(create(:undecided_group_match).winner).to be(nil) + end + end + context '#teams' do before do @playoff_match = create(:running_playoff_match) diff --git a/spec/services/playoff_stage_service_spec.rb b/spec/services/playoff_stage_service_spec.rb index 46dfe55..91e3149 100644 --- a/spec/services/playoff_stage_service_spec.rb +++ b/spec/services/playoff_stage_service_spec.rb @@ -80,4 +80,108 @@ RSpec.describe PlayoffStageService do end end end + + describe '#populate_match_below' do + before :each do + @tournament = create(:stage_tournament, stage_count: 2) + @match = @tournament.stages.find { |s| s.level == 2 }.matches.first + @match.state = :finished + @match.match_scores.each_with_index do |ms, i| + ms.points = i + ms.save + end + @match.save + @companion_match = @tournament.stages.find { |s| s.level == 2 }.matches.second + @companion_match.match_scores.each_with_index do |ms, i| + ms.points = i + ms.save + end + @match_to_find = @tournament.stages.find { |s| s.level == 1 }.matches.first + end + + context 'match below has no match_scores' do + before do + @match_to_find.match_scores = [] + @match_to_find.save + @test = PlayoffStageService.populate_match_below(@match).first + end + + it 'finds the correct match and adds two new match_scores to it' do + expect(@match_to_find.teams).to match_array(@match.winner) + end + + it 'finds the correct match and changes its state' do + expect(@match_to_find.state).to eq('not_ready') + end + end + + context 'match below has one match_score with the winning team' do + before do + @match_to_find.match_scores = create_list(:match_score, 1, team: @match.winner) + @match_to_find.save + @test = PlayoffStageService.populate_match_below(@match).first + end + + it 'finds the correct match and adds no match_score' do + expect(@test.teams).to match_array(@match.winner) + end + + it 'finds the correct match and changes its state' do + expect(@test.state).to eq('not_ready') + end + end + + context 'match below has one match_score with an unknown team' do + before do + @match_to_find.match_scores = create_list(:match_score, 1, team: create(:team), points: 1337) + @match_to_find.save + @test = PlayoffStageService.populate_match_below(@match).first + end + + it 'finds the correct match and replaces the match_score' do + expect(@test.teams).to match_array(@match.winner) + expect(@test.match_scores.first.points).to_not be(1337) + end + + it 'finds the correct match and changes its state' do + expect(@test.state).to eq('not_ready') + end + end + + context 'match below has one match_score with the correct team' do + before do + @match_to_find.match_scores = create_list(:match_score, 1, team: @match.winner, points: 42) + @match_to_find.save + @test = PlayoffStageService.populate_match_below(@match).first + end + + it 'finds the correct match and replaces nothing' do + expect(@test.teams).to match_array(@match.winner) + expect(@test.match_scores.first.points).to be(42) + end + + it 'finds the correct match and changes its state' do + expect(@test.state).to eq('not_ready') + end + end + + context 'match below has two match_scores with the correct teams' do + before do + @companion_match.state = :finished + @companion_match.save + @match_to_find.match_scores = [create(:match_score, team: @match.winner), + create(:match_score, team: @companion_match.winner)] + @match_to_find.save + @test = PlayoffStageService.populate_match_below(@match).first + end + + it 'finds the correct match and replaces nothing' do + expect(@test.teams).to match_array([@match.winner, @companion_match.winner]) + end + + it 'finds the correct match and changes its state' do + expect(@test.state).to eq('not_started') + end + end + end end