From 2575feba80baef595692c9960a0b3b1595e2f6c6 Mon Sep 17 00:00:00 2001 From: Jacob Cody Wimer Date: Wed, 7 May 2025 13:11:19 -0400 Subject: [PATCH] Added a daily recurring job to cleanup tournaments . --- app/jobs/tournament_cleanup_job.rb | 36 ++++++++ bin/rails-dev-run.sh | 2 + config/recurring.yml | 21 ++--- .../cypress/e2e/02-create_tournament.cy.js | 8 +- db/seeds.rb | 13 +-- deploy/deploy-test.sh | 2 +- deploy/docker-compose-test.yml | 7 ++ test/fixtures/tournaments.yml | 11 +++ test/jobs/tournament_cleanup_job_test.rb | 83 +++++++++++++++++++ 9 files changed, 166 insertions(+), 17 deletions(-) create mode 100644 app/jobs/tournament_cleanup_job.rb create mode 100644 test/jobs/tournament_cleanup_job_test.rb diff --git a/app/jobs/tournament_cleanup_job.rb b/app/jobs/tournament_cleanup_job.rb new file mode 100644 index 0000000..769650e --- /dev/null +++ b/app/jobs/tournament_cleanup_job.rb @@ -0,0 +1,36 @@ +class TournamentCleanupJob < ApplicationJob + queue_as :default + + def perform + # Remove or clean up tournaments based on age and match status + process_old_tournaments + end + + private + + def process_old_tournaments + # Get all tournaments older than 1 week that have a user_id + old_tournaments = Tournament.where('date < ? AND user_id IS NOT NULL', 1.week.ago.to_date) + + old_tournaments.each do |tournament| + # Check if it has any non-BYE finished matches + has_real_matches = tournament.matches.where(finished: 1).where.not(win_type: 'BYE').exists? + + if has_real_matches + + # 1. Remove all school delegates + tournament.schools.each do |school| + school.delegates.destroy_all + end + + # 2. Remove all tournament delegates + tournament.delegates.destroy_all + + # 3. Set user_id to null + tournament.update(user_id: nil) + else + tournament.destroy + end + end + end +end \ No newline at end of file diff --git a/bin/rails-dev-run.sh b/bin/rails-dev-run.sh index 98109be..2eb0b78 100755 --- a/bin/rails-dev-run.sh +++ b/bin/rails-dev-run.sh @@ -17,4 +17,6 @@ docker build -t $1 -f ${project_dir}/deploy/rails-dev-Dockerfile \ docker run --rm -it -p 3000:3000 \ -v ${project_dir}:/rails \ + -v /etc/localtime:/etc/localtime:ro \ + -v /etc/timezone:/etc/timezone:ro \ $1 /bin/bash \ No newline at end of file diff --git a/config/recurring.yml b/config/recurring.yml index d045b19..3a6845a 100644 --- a/config/recurring.yml +++ b/config/recurring.yml @@ -1,10 +1,11 @@ -# production: -# periodic_cleanup: -# class: CleanSoftDeletedRecordsJob -# queue: background -# args: [ 1000, { batch_size: 500 } ] -# schedule: every hour -# periodic_command: -# command: "SoftDeletedRecord.due.delete_all" -# priority: 2 -# schedule: at 5am every day +production: + tournament_cleanup_job: + class: TournamentCleanupJob + schedule: every day at 3am + queue: default + +development: + tournament_cleanup_job: + class: TournamentCleanupJob + schedule: every day at 3am + queue: default \ No newline at end of file diff --git a/cypress-tests/cypress/e2e/02-create_tournament.cy.js b/cypress-tests/cypress/e2e/02-create_tournament.cy.js index 15dc4de..eb33501 100644 --- a/cypress-tests/cypress/e2e/02-create_tournament.cy.js +++ b/cypress-tests/cypress/e2e/02-create_tournament.cy.js @@ -27,7 +27,13 @@ describe('Create a tournament', () => { cy.get('input[name="tournament[address]"]').type('123 Wrestling Way'); cy.get('input[name="tournament[director]"]').type('John Doe'); cy.get('input[name="tournament[director_email]"]').type('john.doe@example.com'); - cy.get('input[name="tournament[date]"]').type('2024-12-31'); + + // Set date to 1 month from today dynamically + const futureDate = new Date(); + futureDate.setMonth(futureDate.getMonth() + 1); + const formattedDate = futureDate.toISOString().split('T')[0]; // Format as YYYY-MM-DD + cy.get('input[name="tournament[date]"]').type(formattedDate); + cy.get('select[name="tournament[tournament_type]"]').select('Pool to bracket'); // cy.get('input[name="tournament[is_public]"]').check(); diff --git a/db/seeds.rb b/db/seeds.rb index fadf44c..3cb76de 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -30,8 +30,11 @@ User.create(id: 1, email: 'test@test.com', password: 'password', password_confirmation: 'password') + # Set tournament date to a month from today + future_date = 1.month.from_now.to_date + # Pool to bracket - tournament = Tournament.create(id: 200, name: 'Pool to bracket', address: 'some place', director: 'some guy', director_email: 'their@email.com', tournament_type: 'Pool to bracket', user_id: 1, date: Date.today, is_public: true) + tournament = Tournament.create(id: 200, name: 'Pool to bracket', address: 'some place', director: 'some guy', director_email: 'their@email.com', tournament_type: 'Pool to bracket', user_id: 1, date: future_date, is_public: true) create_schools(tournament, 24) weight_classes=Weight::HS_WEIGHT_CLASSES.split(",") tournament.create_pre_defined_weights(weight_classes) @@ -58,7 +61,7 @@ end # Modified 16 Man Double Elimination 1-6 - tournament = Tournament.create(id: 201, name: 'Modified 16 Man Double Elimination 1-6', address: 'some place', director: 'some guy', director_email: 'their@email.com', tournament_type: 'Modified 16 Man Double Elimination 1-6', user_id: 1, date: Date.today, is_public: true) + tournament = Tournament.create(id: 201, name: 'Modified 16 Man Double Elimination 1-6', address: 'some place', director: 'some guy', director_email: 'their@email.com', tournament_type: 'Modified 16 Man Double Elimination 1-6', user_id: 1, date: future_date, is_public: true) create_schools(tournament, 16) weight_classes=Weight::HS_WEIGHT_CLASSES.split(",") tournament.create_pre_defined_weights(weight_classes) @@ -69,7 +72,7 @@ end # Modified 16 Man Double Elimination 1-8 - tournament = Tournament.create(id: 202, name: 'Modified 16 Man Double Elimination 1-8', address: 'some place', director: 'some guy', director_email: 'their@email.com', tournament_type: 'Modified 16 Man Double Elimination 1-8', user_id: 1, date: Date.today, is_public: true) + tournament = Tournament.create(id: 202, name: 'Modified 16 Man Double Elimination 1-8', address: 'some place', director: 'some guy', director_email: 'their@email.com', tournament_type: 'Modified 16 Man Double Elimination 1-8', user_id: 1, date: future_date, is_public: true) create_schools(tournament, 16) weight_classes=Weight::HS_WEIGHT_CLASSES.split(",") tournament.create_pre_defined_weights(weight_classes) @@ -86,7 +89,7 @@ end # Regular Double Elimination 1-6 - tournament = Tournament.create(id: 203, name: 'Regular Double Elimination 1-6', address: 'some place', director: 'some guy', director_email: 'their@email.com', tournament_type: 'Regular Double Elimination 1-6', user_id: 1, date: Date.today, is_public: true) + tournament = Tournament.create(id: 203, name: 'Regular Double Elimination 1-6', address: 'some place', director: 'some guy', director_email: 'their@email.com', tournament_type: 'Regular Double Elimination 1-6', user_id: 1, date: future_date, is_public: true) create_schools(tournament, 32) weight_classes=Weight::HS_WEIGHT_CLASSES.split(",") tournament.create_pre_defined_weights(weight_classes) @@ -109,7 +112,7 @@ end # Regular Double Elimination 1-8 - tournament = Tournament.create(id: 204, name: 'Regular Double Elimination 1-8', address: 'some place', director: 'some guy', director_email: 'their@email.com', tournament_type: 'Regular Double Elimination 1-8', user_id: 1, date: Date.today, is_public: true) + tournament = Tournament.create(id: 204, name: 'Regular Double Elimination 1-8', address: 'some place', director: 'some guy', director_email: 'their@email.com', tournament_type: 'Regular Double Elimination 1-8', user_id: 1, date: future_date, is_public: true) create_schools(tournament, 32) weight_classes=Weight::HS_WEIGHT_CLASSES.split(",") tournament.create_pre_defined_weights(weight_classes) diff --git a/deploy/deploy-test.sh b/deploy/deploy-test.sh index c00dbe6..fd6529d 100755 --- a/deploy/deploy-test.sh +++ b/deploy/deploy-test.sh @@ -31,7 +31,7 @@ docker-compose -f ${project_dir}/deploy/docker-compose-test.yml up -d # DISABLE_DATABASE_ENVIRONMENT_CHECK=1 is needed because this is "destructive" action on production echo Resetting the db with seed data -docker-compose -f ${project_dir}/deploy/docker-compose-test.yml exec -T app bash -c "DISABLE_DATABASE_ENVIRONMENT_CHECK=1 rake db:reset" +docker-compose -f ${project_dir}/deploy/docker-compose-test.yml run --rm app bash -c "DISABLE_DATABASE_ENVIRONMENT_CHECK=1 rake db:reset" # echo Simulating tournament 204 # docker-compose -f ${project_dir}/deploy/docker-compose-test.yml exec -T app rails tournament:assign_random_wins diff --git a/deploy/docker-compose-test.yml b/deploy/docker-compose-test.yml index d806380..04b21b8 100644 --- a/deploy/docker-compose-test.yml +++ b/deploy/docker-compose-test.yml @@ -36,6 +36,9 @@ services: - "443:443" healthcheck: test: curl http://127.0.0.1/ + volumes: + - /etc/localtime:/etc/localtime:ro + - /etc/timezone:/etc/timezone:ro db: image: mariadb:10.10 @@ -43,6 +46,8 @@ services: - "3306:3306" volumes: - mysql:/var/lib/mysql + - /etc/localtime:/etc/localtime:ro + - /etc/timezone:/etc/timezone:ro environment: - MYSQL_ROOT_PASSWORD=password restart: always @@ -65,3 +70,5 @@ services: metrics: volumes: - influxdb:/var/lib/influxdb + - /etc/localtime:/etc/localtime:ro + - /etc/timezone:/etc/timezone:ro diff --git a/test/fixtures/tournaments.yml b/test/fixtures/tournaments.yml index 2287e6d..9f1175a 100644 --- a/test/fixtures/tournaments.yml +++ b/test/fixtures/tournaments.yml @@ -9,4 +9,15 @@ one: tournament_type: Pool to bracket user_id: 1 date: 2015-12-30 + is_public: true + +two: + id: 2 + name: Test Tournament 2 + address: Some Place + director: Jacob Cody Wimer + director_email: jacob.wimer@gmail.com + tournament_type: Pool to bracket + user_id: 1 + date: 2015-12-30 is_public: true \ No newline at end of file diff --git a/test/jobs/tournament_cleanup_job_test.rb b/test/jobs/tournament_cleanup_job_test.rb new file mode 100644 index 0000000..1f6d370 --- /dev/null +++ b/test/jobs/tournament_cleanup_job_test.rb @@ -0,0 +1,83 @@ +require 'test_helper' + +class TournamentCleanupJobTest < ActiveJob::TestCase + setup do + # Create an old empty tournament (1 week old, 0 finished matches) + @old_empty_tournament = tournaments(:one) + @old_empty_tournament.update(date: 2.weeks.ago.to_date) + + # Create an old active tournament (1 week old, with finished matches) + @old_active_tournament = tournaments(:two) + @old_active_tournament.update(date: 2.weeks.ago.to_date) + + # Add a finished match to the active tournament using create instead of fixtures + weight = Weight.create(max: 120, tournament: @old_active_tournament) + wrestler = Wrestler.create(name: "Test Wrestler", weight: weight, school: schools(:one)) + + @match = Match.create( + tournament: @old_active_tournament, + weight: weight, + bracket_position: "Pool", + round: 1, + finished: 1, + win_type: "Decision", + score: "10-5", + w1: wrestler.id, + winner_id: wrestler.id + ) + + # Add delegates to test removal + tournament_delegate = TournamentDelegate.create(tournament: @old_active_tournament, user: users(:one)) + school = schools(:one) + school.update(tournament_id: @old_active_tournament.id) + school_delegate = SchoolDelegate.create(school: school, user: users(:one)) + end + + test "removes old empty tournaments" do + # In this test, only the empty tournament should be deleted + @match.update!(win_type: "Decision") # Ensure this tournament has a non-BYE match + + assert_difference 'Tournament.count', -1 do + TournamentCleanupJob.perform_now + end + + assert_raises(ActiveRecord::RecordNotFound) { @old_empty_tournament.reload } + assert_nothing_raised { @old_active_tournament.reload } + end + + test "removes old empty tournaments with only a bye finished match" do + # Update the win_type to BYE and score to empty as required by validation + @match.update!(win_type: "BYE", score: "") + assert_equal "BYE", @match.reload.win_type + assert_equal "", @match.score + + # Both tournaments should be deleted (the empty one and the one with only BYE matches) + assert_difference 'Tournament.count', -2 do + TournamentCleanupJob.perform_now + end + + assert_raises(ActiveRecord::RecordNotFound) { @old_empty_tournament.reload } + assert_raises(ActiveRecord::RecordNotFound) { @old_active_tournament.reload } + end + + test "cleans up old active tournaments" do + # Ensure this tournament has a non-BYE match + @match.update!(win_type: "Decision") + + TournamentCleanupJob.perform_now + + # Tournament should still exist + @old_active_tournament.reload + + # User association should be removed + assert_nil @old_active_tournament.user_id + + # Tournament delegates should be removed + assert_equal 0, @old_active_tournament.delegates.count + + # School delegates should be removed + @old_active_tournament.schools.each do |school| + assert_equal 0, school.delegates.count + end + end +end \ No newline at end of file