From ad8e486205a3222c96b14eb2351541da697d3c06 Mon Sep 17 00:00:00 2001 From: Jacob Cody Wimer Date: Mon, 25 Nov 2024 16:25:59 -0500 Subject: [PATCH] Added mat assignment rules for the bout board and fixed the bug where a delegate making the tournamnet info public changes them to the owner --- .../mat_assignment_rules_controller.rb | 82 +++++++ app/controllers/tournaments_controller.rb | 12 +- app/models/mat.rb | 32 ++- app/models/mat_assignment_rule.rb | 4 + app/models/tournament.rb | 58 +++-- .../generate_tournament_matches.rb | 14 +- app/views/layouts/_tournament-navbar.html.erb | 2 + app/views/mat_assignment_rules/_form.html.erb | 61 ++++++ app/views/mat_assignment_rules/edit.html.erb | 3 + app/views/mat_assignment_rules/index.html.erb | 36 ++++ app/views/mat_assignment_rules/new.html.erb | 3 + app/views/tournaments/_form.html.erb | 1 - config/routes.rb | 7 +- ...41027203209_create_mat_assignment_rules.rb | 28 +++ db/schema.rb | 16 +- .../mat_assignment_rules_controller_test.rb | 203 ++++++++++++++++++ .../tournaments_controller_test.rb | 14 ++ test/integration/mat_assignment_rules_test.rb | 195 +++++++++++++++++ test/test_helper.rb | 41 +++- 19 files changed, 769 insertions(+), 43 deletions(-) create mode 100644 app/controllers/mat_assignment_rules_controller.rb create mode 100644 app/models/mat_assignment_rule.rb create mode 100644 app/views/mat_assignment_rules/_form.html.erb create mode 100644 app/views/mat_assignment_rules/edit.html.erb create mode 100644 app/views/mat_assignment_rules/index.html.erb create mode 100644 app/views/mat_assignment_rules/new.html.erb create mode 100644 db/migrate/20241027203209_create_mat_assignment_rules.rb create mode 100644 test/controllers/mat_assignment_rules_controller_test.rb create mode 100644 test/integration/mat_assignment_rules_test.rb diff --git a/app/controllers/mat_assignment_rules_controller.rb b/app/controllers/mat_assignment_rules_controller.rb new file mode 100644 index 0000000..4fda349 --- /dev/null +++ b/app/controllers/mat_assignment_rules_controller.rb @@ -0,0 +1,82 @@ +class MatAssignmentRulesController < ApplicationController + before_action :set_tournament + before_action :check_access_manage + before_action :set_mat_assignment_rule, only: [:edit, :update, :show, :destroy] + + def index + @mat_assignment_rules = @tournament.mat_assignment_rules + # Create a hash mapping each Weight ID to its Weight object for quick lookups + @weights_by_id = @tournament.weights.index_by(&:id) + end + + def new + @mat_assignment_rule = @tournament.mat_assignment_rules.build + load_form_data + end + + def destroy + @mat_assignment_rule.destroy + redirect_to tournament_mat_assignment_rules_path(@tournament), notice: 'Mat assignment rule was successfully deleted.' + end + + def create + @mat_assignment_rule = @tournament.mat_assignment_rules.build(mat_assignment_rule_params) + load_form_data + + if @mat_assignment_rule.save + redirect_to tournament_mat_assignment_rules_path(@tournament), notice: 'Mat assignment rule created successfully.' + else + render :new + end + end + + def edit + load_form_data + end + + def update + load_form_data + + if @mat_assignment_rule.update(mat_assignment_rule_params) + redirect_to tournament_mat_assignment_rules_path(@tournament), notice: 'Mat assignment rule updated successfully.' + else + render :edit + end + end + + private + + def set_tournament + @tournament = Tournament.find(params[:tournament_id]) + end + + def set_mat_assignment_rule + @mat_assignment_rule = @tournament.mat_assignment_rules.find(params[:id]) + end + + def check_access_manage + authorize! :manage, @tournament + end + + def mat_assignment_rule_params + # Set defaults to empty arrays if no checkboxes are selected + # otherwise rails interprets this as "don't modify" + params[:mat_assignment_rule][:weight_classes] ||= [] + params[:mat_assignment_rule][:bracket_positions] ||= [] + params[:mat_assignment_rule][:rounds] ||= [] + + # Permit the parameters and convert specific fields to integer arrays + params.require(:mat_assignment_rule).permit(:mat_id, weight_classes: [], bracket_positions: [], rounds: []).tap do |whitelisted| + whitelisted[:weight_classes] = whitelisted[:weight_classes].map(&:to_i) + whitelisted[:bracket_positions] = whitelisted[:bracket_positions].map(&:to_i) + whitelisted[:rounds] = whitelisted[:rounds].map(&:to_i) + end + end + + # Load data needed for the form + def load_form_data + @available_mats = Mat.all + @unique_bracket_positions = @tournament.matches.select(:bracket_position).distinct.pluck(:bracket_position) + @unique_rounds = @tournament.matches.select(:round).distinct.pluck(:round) + end +end diff --git a/app/controllers/tournaments_controller.rb b/app/controllers/tournaments_controller.rb index 6670647..c07ec52 100644 --- a/app/controllers/tournaments_controller.rb +++ b/app/controllers/tournaments_controller.rb @@ -1,6 +1,6 @@ class TournamentsController < ApplicationController - before_action :set_tournament, only: [:calculate_team_scores, :import,:export,:bout_sheets,:swap,:weigh_in_sheet,:error,:teampointadjust,:remove_teampointadjust,:remove_school_delegate,:remove_delegate,:school_delegate,:delegate,:matches,:weigh_in,:weigh_in_weight,:create_custom_weights,:show,:edit,:update,:destroy,:up_matches,:no_matches,:team_scores,:brackets,:generate_matches,:bracket,:all_brackets] - before_action :check_access_manage, only: [:calculate_team_scores, :import,:export,:swap,:weigh_in_sheet,:teampointadjust,:remove_teampointadjust,:remove_school_delegate,:school_delegate,:weigh_in,:weigh_in_weight,:create_custom_weights,:update,:edit,:generate_matches,:matches] + before_action :set_tournament, only: [:reset_bout_board,:calculate_team_scores, :import,:export,:bout_sheets,:swap,:weigh_in_sheet,:error,:teampointadjust,:remove_teampointadjust,:remove_school_delegate,:remove_delegate,:school_delegate,:delegate,:matches,:weigh_in,:weigh_in_weight,:create_custom_weights,:show,:edit,:update,:destroy,:up_matches,:no_matches,:team_scores,:brackets,:generate_matches,:bracket,:all_brackets] + before_action :check_access_manage, only: [:reset_bout_board,:calculate_team_scores, :import,:export,:swap,:weigh_in_sheet,:teampointadjust,:remove_teampointadjust,:remove_school_delegate,:school_delegate,:weigh_in,:weigh_in_weight,:create_custom_weights,:update,:edit,:generate_matches,:matches] before_action :check_access_destroy, only: [:destroy,:delegate,:remove_delegate] before_action :check_tournament_errors, only: [:generate_matches] before_action :check_for_matches, only: [:up_matches,:bracket,:all_brackets] @@ -248,6 +248,7 @@ class TournamentsController < ApplicationController redirect_to root_path end @tournament = Tournament.new(tournament_params) + @tournament.user_id = current_user.id respond_to do |format| if @tournament.save format.html { redirect_to @tournament, notice: 'Tournament was successfully created.' } @@ -282,6 +283,11 @@ class TournamentsController < ApplicationController def error end + def reset_bout_board + @tournament.reset_and_fill_bout_board + redirect_to tournament_path(@tournament), notice: "Successfully reset the bout board." + end + private # Use callbacks to share common setup or constraints between actions. def set_tournament @@ -290,7 +296,7 @@ class TournamentsController < ApplicationController # Never trust parameters from the scary internet, only allow the white list through. def tournament_params - params.require(:tournament).permit(:name, :address, :director, :director_email, :tournament_type, :weigh_in_ref, :user_id, :date, :originalId, :swapId, :is_public) + params.require(:tournament).permit(:name, :address, :director, :director_email, :tournament_type, :weigh_in_ref, :date, :originalId, :swapId, :is_public) end #Check for tournament owner diff --git a/app/models/mat.rb b/app/models/mat.rb index 1f1ba48..991c5dc 100644 --- a/app/models/mat.rb +++ b/app/models/mat.rb @@ -1,6 +1,7 @@ class Mat < ApplicationRecord belongs_to :tournament has_many :matches + has_many :mat_assignment_rules, dependent: :destroy validates :name, presence: true @@ -21,11 +22,11 @@ class Mat < ApplicationRecord end def assign_next_match - t_matches = tournament.matches.select{|m| m.mat_id == nil && m.finished != 1 && m.bout_number != nil}.sort_by{|m| m.bout_number} - if t_matches.size > 0 and self.unfinished_matches.size < 4 - match = t_matches.sort_by{|m| m.bout_number}.first + match = next_eligible_match + if match match.mat_id = self.id if match.save + # Invalidate any wrestler caches if match.w1 match.wrestler1.touch match.wrestler1.school.touch @@ -43,6 +44,31 @@ class Mat < ApplicationRecord end end + def next_eligible_match + # Start with all matches that are either unfinished (nil or 0), have a bout number, and are ordered by bout_number + filtered_matches = tournament.matches.where(finished: [nil, 0]).where(mat_id: nil).where.not(bout_number: nil).order(:bout_number) + + # Sequentially apply each rule, narrowing down the matches + mat_assignment_rules.each do |rule| + if rule.weight_classes.any? + filtered_matches = filtered_matches.where(weight_id: rule.weight_classes) + end + + if rule.bracket_positions.any? + filtered_matches = filtered_matches.where(bracket_position: rule.bracket_positions) + end + + if rule.rounds.any? + filtered_matches = filtered_matches.where(round: rule.rounds) + end + end + + # Return the first match in filtered results, or nil if none are left + result = filtered_matches.first + result + end + + def unfinished_matches matches.select{|m| m.finished != 1}.sort_by{|m| m.bout_number} end diff --git a/app/models/mat_assignment_rule.rb b/app/models/mat_assignment_rule.rb new file mode 100644 index 0000000..85cdc29 --- /dev/null +++ b/app/models/mat_assignment_rule.rb @@ -0,0 +1,4 @@ +class MatAssignmentRule < ApplicationRecord + belongs_to :mat + belongs_to :tournament + end \ No newline at end of file diff --git a/app/models/tournament.rb b/app/models/tournament.rb index 43fbbdd..f743dbf 100644 --- a/app/models/tournament.rb +++ b/app/models/tournament.rb @@ -7,6 +7,7 @@ class Tournament < ApplicationRecord has_many :wrestlers, through: :weights has_many :matches, dependent: :destroy has_many :delegates, class_name: "TournamentDelegate" + has_many :mat_assignment_rules, dependent: :destroy validates :date, :name, :tournament_type, :address, :director, :director_email , presence: true @@ -17,25 +18,25 @@ class Tournament < ApplicationRecord end def self.search_date_name(pattern) - if pattern.blank? # blank? covers both nil and empty string - all - else - search_functions = [] - search_variables = [] - search_terms = pattern.split(' ').map{|word| "%#{word.downcase}%"} - search_terms.each do |word| - search_functions << '(LOWER(name) LIKE ? or LOWER(date) LIKE ?)' - # add twice for both ?'s in the function above - search_variables << word - search_variables << word - end - like_patterns = search_functions.join(' and ') - # puts "where(#{like_patterns})" - # puts *search_variables - # example: (LOWER(name LIKE ? or LOWER(date) LIKE ?) and (LOWER(name) LIKE ? or LOWER(date) LIKE ?), %test%, %test%, %2016%, %2016% - where("#{like_patterns}", *search_variables) - end -end + if pattern.blank? # blank? covers both nil and empty string + all + else + search_functions = [] + search_variables = [] + search_terms = pattern.split(' ').map{|word| "%#{word.downcase}%"} + search_terms.each do |word| + search_functions << '(LOWER(name) LIKE ? or LOWER(date) LIKE ?)' + # add twice for both ?'s in the function above + search_variables << word + search_variables << word + end + like_patterns = search_functions.join(' and ') + # puts "where(#{like_patterns})" + # puts *search_variables + # example: (LOWER(name LIKE ? or LOWER(date) LIKE ?) and (LOWER(name) LIKE ? or LOWER(date) LIKE ?), %test%, %test%, %2016%, %2016% + where("#{like_patterns}", *search_variables) + end + end def days_until_start time = (Date.today - self.date).to_i @@ -188,4 +189,23 @@ end nil end end + + def reset_and_fill_bout_board + reset_mats + + if mats.any? + 4.times do + # Iterate over each mat and assign the next available match + mats.each do |mat| + match_assigned = mat.assign_next_match + # If no more matches are available, exit early + unless match_assigned + puts "No more eligible matches to assign." + return + end + end + end + end + end + end \ No newline at end of file diff --git a/app/services/tournament_services/generate_tournament_matches.rb b/app/services/tournament_services/generate_tournament_matches.rb index d3ffc63..fd50ddb 100644 --- a/app/services/tournament_services/generate_tournament_matches.rb +++ b/app/services/tournament_services/generate_tournament_matches.rb @@ -16,7 +16,6 @@ class GenerateTournamentMatches @tournament.curently_generating_matches = 1 @tournament.save unAssignBouts - unAssignMats PoolToBracketMatchGeneration.new(@tournament).generatePoolToBracketMatchesWeight(weight) if @tournament.tournament_type == "Pool to bracket" postMatchCreationActions PoolToBracketGenerateLoserNames.new(@tournament).assignLoserNamesWeight(weight) if @tournament.tournament_type == "Pool to bracket" @@ -51,7 +50,7 @@ class GenerateTournamentMatches def postMatchCreationActions moveFinalsMatchesToLastRound assignBouts - assignFirstMatchesToMats + @tournament.reset_and_fill_bout_board @tournament.curently_generating_matches = nil @tournament.save! end @@ -75,17 +74,6 @@ class GenerateTournamentMatches end end - def assignFirstMatchesToMats - matsToAssign = @tournament.mats - if matsToAssign.count > 0 - until matsToAssign.sort_by{|m| m.id}.last.matches.count == 4 - matsToAssign.sort_by{|m| m.id}.each do |m| - m.assign_next_match - end - end - end - end - def unAssignMats matches = @tournament.matches.reload matches.each do |m| diff --git a/app/views/layouts/_tournament-navbar.html.erb b/app/views/layouts/_tournament-navbar.html.erb index 5915715..945d1b6 100644 --- a/app/views/layouts/_tournament-navbar.html.erb +++ b/app/views/layouts/_tournament-navbar.html.erb @@ -32,6 +32,8 @@
  • <%= link_to "All Matches" , "/tournaments/#{@tournament.id}/matches" %>
  • <%= link_to "Full Screen Bout Board" , "/tournaments/#{@tournament.id}/up_matches?print=true" , target: :_blank %>
  • <%= link_to "Deduct Team Points" , "/tournaments/#{@tournament.id}/teampointadjust" %>
  • +
  • <%= link_to "View All Mat Assignment Rules", tournament_mat_assignment_rules_path(@tournament) %>
  • +
  • <%= link_to "Reset Bout Board", reset_bout_board_tournament_path(@tournament), method: :post, data: { confirm: "Are you sure you want to reset the bout board?" } %>
  • <% if can? :destroy, @tournament %>
  • <%= link_to "Tournament Delegation" , "/tournaments/#{@tournament.id}/delegate" %>
  • <% end %> diff --git a/app/views/mat_assignment_rules/_form.html.erb b/app/views/mat_assignment_rules/_form.html.erb new file mode 100644 index 0000000..7f47317 --- /dev/null +++ b/app/views/mat_assignment_rules/_form.html.erb @@ -0,0 +1,61 @@ +<%= form_with model: [@tournament, @mat_assignment_rule] do |form| %> + +
    + <%= form.label :mat_id, "Select Mat" %>
    + <% if @available_mats.any? %> + <%= form.collection_select :mat_id, @available_mats, :id, :name, { prompt: "Choose a Mat" } %> + <% else %> +

    No mats are available. Please create a mat first.

    + <% end %> +
    + + +
    + <%= form.label :weight_classes, "Allowed Weight Classes" %>
    + <% if @tournament.weights.any? %> + <% @tournament.weights.each do |weight| %> +
    + <%= check_box_tag "mat_assignment_rule[weight_classes][]", weight.id, @mat_assignment_rule.weight_classes.include?(weight.id) %> + <%= label_tag "mat_assignment_rule_weight_classes_#{weight.id}", weight.max %> +
    + <% end %> + <% else %> +

    No weight classes are available. Please create weight classes first.

    + <% end %> +
    + + +
    + <%= form.label :bracket_positions, "Allowed Bracket Positions" %>
    + <% if @unique_bracket_positions.present? %> + <% @unique_bracket_positions.each do |position| %> +
    + <%= check_box_tag "mat_assignment_rule[bracket_positions][]", position, @mat_assignment_rule.bracket_positions.include?(position) %> + <%= label_tag "mat_assignment_rule_bracket_positions_#{position}", position %> +
    + <% end %> + <% else %> +

    No bracket positions are available. Please ensure matches have bracket positions set.

    + <% end %> +
    + + +
    + <%= form.label :rounds, "Allowed Rounds" %>
    + <% if @unique_rounds.present? %> + <% @unique_rounds.each do |round| %> +
    + <%= check_box_tag "mat_assignment_rule[rounds][]", round, @mat_assignment_rule.rounds.include?(round) %> + <%= label_tag "mat_assignment_rule_rounds_#{round}", round %> +
    + <% end %> + <% else %> +

    No rounds are available. Please ensure matches have rounds set.

    + <% end %> +
    + + +
    + <%= form.submit 'Submit', :class=>"btn btn-success" %> +
    +<% end %> diff --git a/app/views/mat_assignment_rules/edit.html.erb b/app/views/mat_assignment_rules/edit.html.erb new file mode 100644 index 0000000..de9e8af --- /dev/null +++ b/app/views/mat_assignment_rules/edit.html.erb @@ -0,0 +1,3 @@ +

    Edit Mat Assignment Rule for <%= @tournament.name %>

    + +<%= render "form", tournament: @tournament, mat_assignment: @mat_assignment_rule, available_mats: @available_mats %> \ No newline at end of file diff --git a/app/views/mat_assignment_rules/index.html.erb b/app/views/mat_assignment_rules/index.html.erb new file mode 100644 index 0000000..f501366 --- /dev/null +++ b/app/views/mat_assignment_rules/index.html.erb @@ -0,0 +1,36 @@ + + + + + + + + + + + + <% @mat_assignment_rules.each do |rule| %> + + + + + + + + + + + + + + <% end %> + +
    MatWeight Classes (Max)Bracket PositionsRounds<%= link_to ' New Mat Assignment Rule', new_tournament_mat_assignment_rule_path(@tournament), :class=>"fas fa-plus" %>
    <%= rule.mat.name %> + <% rule.weight_classes.each do |weight_id| %> + <% weight = @weights_by_id[weight_id] %> + <%= weight ? weight.max : "N/A" %><%= ", " unless weight_id == rule.weight_classes.last %> + <% end %> + <%= rule.bracket_positions.join(", ") %><%= rule.rounds.join(", ") %> + <%= link_to '', edit_tournament_mat_assignment_rule_path(@tournament, rule), :class=>"fas fa-edit" %> + <%= link_to '', tournament_mat_assignment_rule_path(@tournament, rule), method: :delete, data: { confirm: "Are you sure?" }, :class=>"fas fa-trash-alt" %> +
    diff --git a/app/views/mat_assignment_rules/new.html.erb b/app/views/mat_assignment_rules/new.html.erb new file mode 100644 index 0000000..69c95b2 --- /dev/null +++ b/app/views/mat_assignment_rules/new.html.erb @@ -0,0 +1,3 @@ +

    New Mat Assignment Rule for <%= @tournament.name %>

    + +<%= render "form", tournament: @tournament, mat_assignment: @mat_assignment_rule, available_mats: @available_mats %> \ No newline at end of file diff --git a/app/views/tournaments/_form.html.erb b/app/views/tournaments/_form.html.erb index af75254..752e0e3 100644 --- a/app/views/tournaments/_form.html.erb +++ b/app/views/tournaments/_form.html.erb @@ -40,7 +40,6 @@ See About page for information on tournament types: <%= link_to "About", "/static_pages/about" %>

    For double elimination tournaments, 1-6 places 1st through 6th and 1-8 places 1st through 8th.

    -<%= f.hidden_field :user_id, :value => current_user.id %>

    diff --git a/config/routes.rb b/config/routes.rb index aeaa149..18017e1 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -6,7 +6,12 @@ Wrestling::Application.routes.draw do devise_for :users - resources :tournaments + resources :tournaments do + resources :mat_assignment_rules, only: [:index, :new, :create, :edit, :update, :show, :destroy] + member do + post :reset_bout_board + end + end resources :schools diff --git a/db/migrate/20241027203209_create_mat_assignment_rules.rb b/db/migrate/20241027203209_create_mat_assignment_rules.rb new file mode 100644 index 0000000..1ba57b7 --- /dev/null +++ b/db/migrate/20241027203209_create_mat_assignment_rules.rb @@ -0,0 +1,28 @@ +class CreateMatAssignmentRules < ActiveRecord::Migration[6.1] + def up + create_table :mat_assignment_rules do |t| + t.references :tournament, null: false, foreign_key: true + t.references :mat, null: false, foreign_key: true + t.json :weight_classes, default: [] + t.json :bracket_positions, default: [] + t.json :rounds, default: [] + + t.timestamps + end + + # Add unique index on mat_id if it does not already exist + add_index :mat_assignment_rules, :mat_id, unique: true unless index_exists?(:mat_assignment_rules, :mat_id) + + # Add index on tournament_id for faster lookups + add_index :mat_assignment_rules, :tournament_id unless index_exists?(:mat_assignment_rules, :tournament_id) + end + + def down + # Remove indexes if they exist + remove_index :mat_assignment_rules, :mat_id if index_exists?(:mat_assignment_rules, :mat_id) + remove_index :mat_assignment_rules, :tournament_id if index_exists?(:mat_assignment_rules, :tournament_id) + + # Drop the table + drop_table :mat_assignment_rules + end +end diff --git a/db/schema.rb b/db/schema.rb index 8fa9650..8429c18 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_03_11_103416) do +ActiveRecord::Schema[7.1].define(version: 2024_10_27_203209) do create_table "delayed_jobs", force: :cascade do |t| t.integer "priority", default: 0, null: false t.integer "attempts", default: 0, null: false @@ -28,6 +28,18 @@ ActiveRecord::Schema[7.1].define(version: 2024_03_11_103416) do t.index ["priority", "run_at"], name: "delayed_jobs_priority" end + create_table "mat_assignment_rules", force: :cascade do |t| + t.integer "tournament_id", null: false + t.integer "mat_id", null: false + t.json "weight_classes", default: [] + t.json "bracket_positions", default: [] + t.json "rounds", default: [] + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["mat_id"], name: "index_mat_assignment_rules_on_mat_id" + t.index ["tournament_id"], name: "index_mat_assignment_rules_on_tournament_id" + end + create_table "matches", force: :cascade do |t| t.integer "w1" t.integer "w2" @@ -156,4 +168,6 @@ ActiveRecord::Schema[7.1].define(version: 2024_03_11_103416) do t.index ["weight_id"], name: "index_wrestlers_on_weight_id" end + add_foreign_key "mat_assignment_rules", "mats" + add_foreign_key "mat_assignment_rules", "tournaments" end diff --git a/test/controllers/mat_assignment_rules_controller_test.rb b/test/controllers/mat_assignment_rules_controller_test.rb new file mode 100644 index 0000000..d9dddd5 --- /dev/null +++ b/test/controllers/mat_assignment_rules_controller_test.rb @@ -0,0 +1,203 @@ +require 'test_helper' + +class MatAssignmentRulesControllerTest < ActionController::TestCase + include Devise::Test::ControllerHelpers + + setup do + @tournament = tournaments(:one) # Existing fixture + @mat = mats(:one) # Existing fixture + end + + # Controller actions for testing + def index + get :index, params: { tournament_id: @tournament.id } + end + + def new + get :new, params: { tournament_id: @tournament.id } + end + + def create_rule + post :create, params: { tournament_id: @tournament.id, mat_assignment_rule: { + mat_id: @mat.id, weight_classes: [1, 2, 3], bracket_positions: ['1/2'], rounds: [1, 2] + } } + end + + def edit_rule(rule_id) + get :edit, params: { tournament_id: @tournament.id, id: rule_id } + end + + def update_rule(rule_id) + patch :update, params: { tournament_id: @tournament.id, id: rule_id, mat_assignment_rule: { + weight_classes: [4, 5, 6] + } } + end + + def destroy_rule(rule_id) + delete :destroy, params: { tournament_id: @tournament.id, id: rule_id } + end + + # User sign-in helpers + def sign_in_owner + sign_in users(:one) + end + + def sign_in_non_owner + sign_in users(:two) + end + + def sign_in_tournament_delegate + sign_in users(:three) + end + + def sign_in_school_delegate + sign_in users(:four) + end + + # Assertion helpers + def success + assert_response :success + end + + def redirect + assert_redirected_to '/static_pages/not_allowed' + end + + # Tests + test "logged in tournament owner should get index" do + sign_in_owner + index + success + end + + test "logged in tournament delegate should get index" do + sign_in_tournament_delegate + index + success + end + + test "logged in user should not get index if not owner or delegate" do + sign_in_non_owner + index + redirect + end + + test "logged school delegate should not get index" do + sign_in_school_delegate + index + redirect + end + + test "logged in tournament owner should create a new rule" do + sign_in_owner + new + success + assert_difference 'MatAssignmentRule.count', 1 do + create_rule + end + assert_redirected_to tournament_mat_assignment_rules_path(@tournament) + end + + test "logged in tournament delegate should create a new rule" do + sign_in_tournament_delegate + new + success + assert_difference 'MatAssignmentRule.count', 1 do + create_rule + end + assert_redirected_to tournament_mat_assignment_rules_path(@tournament) + end + + test "logged in user should not create a rule if not owner or delegate" do + sign_in_non_owner + new + redirect + assert_no_difference 'MatAssignmentRule.count' do + create_rule + end + redirect + end + + test "logged school delegate should not create a rule" do + sign_in_school_delegate + new + redirect + assert_no_difference 'MatAssignmentRule.count' do + create_rule + end + redirect + end + + test "logged in tournament owner should edit a rule" do + rule = MatAssignmentRule.create!(mat_id: @mat.id, tournament_id: @tournament.id, weight_classes: [1, 2, 3]) + sign_in_owner + edit_rule(rule.id) + success + end + + test "logged in tournament delegate should edit a rule" do + rule = MatAssignmentRule.create!(mat_id: @mat.id, tournament_id: @tournament.id, weight_classes: [1, 2, 3]) + sign_in_tournament_delegate + edit_rule(rule.id) + success + end + + test "logged in user should not edit a rule if not owner or delegate" do + rule = MatAssignmentRule.create!(mat_id: @mat.id, tournament_id: @tournament.id, weight_classes: [1, 2, 3]) + sign_in_non_owner + edit_rule(rule.id) + redirect + end + + test "logged school delegate should not edit a rule" do + rule = MatAssignmentRule.create!(mat_id: @mat.id, tournament_id: @tournament.id, weight_classes: [1, 2, 3]) + sign_in_school_delegate + edit_rule(rule.id) + redirect + end + + test "logged in tournament owner should update a rule" do + rule = MatAssignmentRule.create!(mat_id: @mat.id, tournament_id: @tournament.id, weight_classes: [1, 2, 3]) + sign_in_owner + update_rule(rule.id) + assert_redirected_to tournament_mat_assignment_rules_path(@tournament) + rule.reload + assert_equal [4, 5, 6], rule.weight_classes + end + + test "logged in tournament delegate should update a rule" do + rule = MatAssignmentRule.create!(mat_id: @mat.id, tournament_id: @tournament.id, weight_classes: [1, 2, 3]) + sign_in_tournament_delegate + update_rule(rule.id) + assert_redirected_to tournament_mat_assignment_rules_path(@tournament) + rule.reload + assert_equal [4, 5, 6], rule.weight_classes + end + + test "logged in tournament owner should destroy a rule" do + rule = MatAssignmentRule.create!(mat_id: @mat.id, tournament_id: @tournament.id, weight_classes: [1, 2, 3]) + sign_in_owner + assert_difference 'MatAssignmentRule.count', -1 do + destroy_rule(rule.id) + end + assert_redirected_to tournament_mat_assignment_rules_path(@tournament) + end + + test "logged in tournament delegate should destroy a rule" do + rule = MatAssignmentRule.create!(mat_id: @mat.id, tournament_id: @tournament.id, weight_classes: [1, 2, 3]) + sign_in_tournament_delegate + assert_difference 'MatAssignmentRule.count', -1 do + destroy_rule(rule.id) + end + assert_redirected_to tournament_mat_assignment_rules_path(@tournament) + end + + test "logged in user should not destroy a rule if not owner or delegate" do + rule = MatAssignmentRule.create!(mat_id: @mat.id, tournament_id: @tournament.id, weight_classes: [1, 2, 3]) + sign_in_non_owner + assert_no_difference 'MatAssignmentRule.count' do + destroy_rule(rule.id) + end + redirect + end +end diff --git a/test/controllers/tournaments_controller_test.rb b/test/controllers/tournaments_controller_test.rb index f7481f0..f829523 100644 --- a/test/controllers/tournaments_controller_test.rb +++ b/test/controllers/tournaments_controller_test.rb @@ -648,4 +648,18 @@ class TournamentsControllerTest < ActionController::TestCase post :calculate_team_scores, params: { id: 1 } redirect end + + test "only owner can set user_id when creating a new tournament" do + # Sign in as owner and create a new tournament + sign_in_owner + post :create, params: { tournament: { name: 'New Tournament', address: '123 Main St', director: 'Director', director_email: 'director@example.com', date: '2024-01-01', tournament_type: 'Pool to bracket', is_public: false } } + new_tournament = Tournament.last + assert_equal users(:one).id, new_tournament.user_id, "The owner should be assigned as the user_id on creation" + + # Sign in as non-owner and try to update the tournament without changing user_id + sign_in_non_owner + patch :update, params: { id: new_tournament.id, tournament: { name: 'Updated Tournament', is_public: true } } + updated_tournament = Tournament.find(new_tournament.id) + assert_equal users(:one).id, updated_tournament.user_id, "The user_id should not change when the tournament is edited by a non-owner" + end end diff --git a/test/integration/mat_assignment_rules_test.rb b/test/integration/mat_assignment_rules_test.rb new file mode 100644 index 0000000..29af439 --- /dev/null +++ b/test/integration/mat_assignment_rules_test.rb @@ -0,0 +1,195 @@ +require 'test_helper' + +class MatAssignmentRules < ActionDispatch::IntegrationTest + def setup + create_double_elim_tournament_1_6_with_multiple_weights_and_multiple_mats(6, 5, 3) # 6 wrestlers, 5 weights, 3 mats + end + + test "Mat assignment works with no mat assignment rules" do + @tournament.reset_and_fill_bout_board + assert @tournament.mats.first.matches.first != nil + end + + test "Mat assignment only assigns matches for a certain weight" do + assignment_weight_id = @tournament.weights.second.id + mat = @tournament.mats.first + + mat_assignment_rule = MatAssignmentRule.new( + mat_id: mat.id, + weight_classes: [assignment_weight_id], + tournament_id: @tournament.id + ) + assert mat_assignment_rule.save, "Mat assignment rule should be saved successfully" + + @tournament.reload + @tournament.reset_and_fill_bout_board + + mat.reload + assigned_matches = mat.matches.reload + + assert_not_empty assigned_matches, "Matches should have been assigned to the mat" + assert assigned_matches.all? { |match| match.weight_id == assignment_weight_id }, + "All matches assigned to the mat should belong to the specified weight class" + end + + test "Mat assignment only assigns matches for round 2" do + mat = @tournament.mats.first + + mat_assignment_rule = MatAssignmentRule.new( + mat_id: mat.id, + rounds: [2], + tournament_id: @tournament.id + ) + assert mat_assignment_rule.save, "Mat assignment rule should be saved successfully" + + @tournament.reload + @tournament.reset_and_fill_bout_board + + mat.reload + assigned_matches = mat.matches.reload + + assert_not_empty assigned_matches, "Matches should have been assigned to the mat" + assert assigned_matches.all? { |match| match.round == 2 }, + "All matches assigned to the mat should only be for round 2" + end + + test "Mat assignment only assigns matches for bracket position 1/2" do + mat = @tournament.mats.first + + mat_assignment_rule = MatAssignmentRule.new( + mat_id: mat.id, + bracket_positions: ['1/2'], + tournament_id: @tournament.id + ) + assert mat_assignment_rule.save, "Mat assignment rule should be saved successfully" + + @tournament.reload + @tournament.reset_and_fill_bout_board + + mat.reload + assigned_matches = mat.matches.reload + + assert_not_empty assigned_matches, "Matches should have been assigned to the mat" + assert assigned_matches.all? { |match| match.bracket_position == '1/2' }, + "All matches assigned to the mat should only be for bracket_position 1/2" + end + + test "Mat assignment only assigns matches for a certain weight, round, and bracket position" do + assignment_weight_id = @tournament.weights.first.id # Use the first weight + mat = @tournament.mats.first + + last_round = @tournament.matches.maximum(:round) # Dynamically fetch the last round + finals_bracket_position = '1/2' # Finals bracket position + + # Verify that there are matches in the last round with the '1/2' bracket position + relevant_matches = @tournament.matches.where( + weight_id: assignment_weight_id, + round: last_round, + bracket_position: finals_bracket_position + ) + assert_not_empty relevant_matches, "There should be matches in the last round with the '1/2' bracket position" + + mat_assignment_rule = MatAssignmentRule.new( + mat_id: mat.id, + weight_classes: [assignment_weight_id], + rounds: [last_round], # Use the last round dynamically + bracket_positions: [finals_bracket_position], # Use '1/2' as the bracket position + tournament_id: @tournament.id + ) + assert mat_assignment_rule.save, "Mat assignment rule should be saved successfully" + + @tournament.reload + @tournament.reset_and_fill_bout_board + + mat.reload + assigned_matches = mat.matches.reload + + assert_not_empty assigned_matches, "Matches should have been assigned to the mat" + + assert( + assigned_matches.all? do |match| + match.weight_id == assignment_weight_id && + match.round == last_round && + match.bracket_position == finals_bracket_position + end, + "All matches assigned to the mat should satisfy all conditions (weight, round, and bracket position)" + ) + end + + test "No matches assigned when no matches meet rule criteria" do + mat = @tournament.mats.first + + mat_assignment_rule = MatAssignmentRule.new( + mat_id: mat.id, + weight_classes: [-1], # Nonexistent weight ID + tournament_id: @tournament.id + ) + assert mat_assignment_rule.save, "Mat assignment rule should be saved successfully" + + @tournament.reload + @tournament.reset_and_fill_bout_board + + mat.reload + assigned_matches = mat.matches.reload + + assert_empty assigned_matches, "No matches should have been assigned to the mat" + end + + test "Multiple mats follow their respective rules" do + mat1 = @tournament.mats.first + mat2 = @tournament.mats.second + + mat1_rule = MatAssignmentRule.new( + mat_id: mat1.id, + weight_classes: [@tournament.weights.first.id], + tournament_id: @tournament.id + ) + assert mat1_rule.save, "Mat 1 assignment rule should be saved successfully" + + mat2_rule = MatAssignmentRule.new( + mat_id: mat2.id, + rounds: [3], + tournament_id: @tournament.id + ) + assert mat2_rule.save, "Mat 2 assignment rule should be saved successfully" + + @tournament.reload + @tournament.reset_and_fill_bout_board + + mat1.reload + mat2.reload + + mat1_matches = mat1.matches.reload + mat2_matches = mat2.matches.reload + + assert_not_empty mat1_matches, "Matches should have been assigned to Mat 1" + assert_not_empty mat2_matches, "Matches should have been assigned to Mat 2" + + assert mat1_matches.all? { |match| match.weight_id == @tournament.weights.first.id }, + "All matches assigned to Mat 1 should be for the specified weight class" + + assert mat2_matches.all? { |match| match.round == 3 }, + "All matches assigned to Mat 2 should be for the specified round" + end + + test "No matches assigned in an empty tournament" do + # Use the tournament created in the setup + @tournament.matches.destroy_all # Remove all matches to simulate an empty tournament + + mat = @tournament.mats.first + + mat_assignment_rule = MatAssignmentRule.new( + mat_id: mat.id, + rounds: [1], # Arbitrary round; no matches should exist anyway + tournament_id: @tournament.id + ) + assert mat_assignment_rule.save, "Mat assignment rule should be saved successfully" + + @tournament.reset_and_fill_bout_board + + mat.reload + assigned_matches = mat.matches.reload + + assert_empty assigned_matches, "No matches should have been assigned for an empty tournament" + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index 2f4ab25..44d4761 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -104,16 +104,53 @@ class ActiveSupport::TestCase return @tournament end + def create_double_elim_tournament_1_6_with_multiple_weights_and_multiple_mats(number_of_wrestlers, number_of_weights, number_of_mats) + @tournament = Tournament.new + @tournament.name = "Test Tournament" + @tournament.address = "some place" + @tournament.director = "some guy" + @tournament.director_email = "test@test.com" + @tournament.tournament_type = "Regular Double Elimination 1-6" + @tournament.date = "2015-12-30" + @tournament.is_public = true + @tournament.save + + @school = School.new + @school.name = "Test" + @school.tournament_id = @tournament.id + @school.save + + @mats = [] + (1..number_of_mats).each do |mat_number| + mat = Mat.new + mat.name = "Mat #{mat_number}" + mat.tournament_id = @tournament.id + mat.save + @mats << mat + end + + (1..number_of_weights).each do |weight_number| + weight = Weight.new + weight.max = 100 + weight_number + weight.tournament_id = @tournament.id + weight.save + create_wrestlers_for_weight_for_double_elim(weight, @school, number_of_wrestlers, 1) + end + + GenerateTournamentMatches.new(@tournament).generate + return @tournament + end + def create_wrestlers_for_weight_for_double_elim(weight, school, number_of_wrestlers, naming_start_number) naming_number = naming_start_number - for number in (1..number_of_wrestlers) do + (1..number_of_wrestlers).each do |number| wrestler = Wrestler.new wrestler.name = "Test#{naming_number}" wrestler.school_id = school.id wrestler.weight_id = weight.id wrestler.original_seed = naming_number wrestler.save - naming_number = naming_number + 1 + naming_number += 1 end end