diff --git a/app/controllers/tournament_backups_controller.rb b/app/controllers/tournament_backups_controller.rb
new file mode 100644
index 0000000..0a9992b
--- /dev/null
+++ b/app/controllers/tournament_backups_controller.rb
@@ -0,0 +1,78 @@
+class TournamentBackupsController < ApplicationController
+ before_action :set_tournament
+ before_action :set_tournament_backup, only: [:show, :destroy, :restore]
+ before_action :check_access_manage
+
+ # GET /tournament/:tournament_id/tournament_backups
+ def index
+ @tournament_backups = @tournament.tournament_backups.order(created_at: :desc)
+ end
+
+ # GET /tournament/:tournament_id/tournament_backups/:id
+ def show
+ end
+
+ # DELETE /tournament/:tournament_id/tournament_backups/:id
+ def destroy
+ if @tournament_backup.destroy
+ redirect_to tournament_tournament_backups_path(@tournament), notice: 'Backup was successfully deleted.'
+ else
+ redirect_to tournament_tournament_backups_path(@tournament), alert: 'Failed to delete the backup.'
+ end
+ end
+
+ # POST /tournament/:tournament_id/tournament_backups/create
+ def create
+ TournamentBackupService.new(@tournament, 'Manual backup').create_backup
+ redirect_to tournament_tournament_backups_path(@tournament), notice: 'Backup was successfully created. It will show up soon, check your background jobs for status.'
+ end
+
+ # POST /tournament/:tournament_id/tournament_backups/:id/restore
+ def restore
+ WrestlingdevImporter.new(@tournament, @tournament_backup).import
+ redirect_to tournament_path(@tournament), notice: 'Restore has successfully been submitted, please check your background jobs to see if it has finished.'
+ end
+
+ # POST /tournament/:tournament_id/tournament_backups/import_manual
+ def import_manual
+ import_text = params[:tournament][:import_text]
+ if import_text.blank?
+ redirect_to tournament_tournament_backups_path(@tournament), alert: 'Import text cannot be blank.'
+ return
+ end
+
+ begin
+
+ # Create a temporary backup object
+ backup = TournamentBackup.new(
+ tournament: @tournament,
+ backup_data: Base64.encode64(import_text),
+ backup_reason: 'Manual Import'
+ )
+
+ # Pass the backup object to the importer
+ WrestlingdevImporter.new(@tournament, backup).import
+
+ redirect_to tournament_path(@tournament), notice: 'Restore has successfully been submitted, please check your background jobs to see if it has finished.'
+ rescue JSON::ParserError => e
+ redirect_to tournament_tournament_backups_path(@tournament), alert: "Failed to parse JSON: #{e.message}"
+ rescue StandardError => e
+ redirect_to tournament_tournament_backups_path(@tournament), alert: "An error occurred: #{e.message}"
+ end
+ end
+
+ private
+
+ def set_tournament
+ @tournament = Tournament.find(params[:tournament_id])
+ end
+
+ def set_tournament_backup
+ @tournament_backup = @tournament.tournament_backups.find(params[:id])
+ end
+
+ def check_access_manage
+ authorize! :manage, @tournament
+ end
+ end
+
\ No newline at end of file
diff --git a/app/controllers/tournaments_controller.rb b/app/controllers/tournaments_controller.rb
index c07ec52..6332e53 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: [: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 :set_tournament, only: [:reset_bout_board,:calculate_team_scores,: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,: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]
@@ -10,10 +10,6 @@ class TournamentsController < ApplicationController
end
- def export
-
- end
-
def calculate_team_scores
respond_to do |format|
if @tournament.calculate_all_team_scores
@@ -23,16 +19,6 @@ class TournamentsController < ApplicationController
end
end
- def import
- import_text = params[:tournament][:import_text]
- respond_to do |format|
- if WrestlingdevImporter.new(@tournament,import_text).import
- format.html { redirect_to "/tournaments/#{@tournament.id}", notice: 'Import is on-going. This will take 1-5 minutes.' }
- format.json { head :no_content }
- end
- end
- end
-
def swap
@wrestler = Wrestler.find(params[:wrestler][:originalId])
respond_to do |format|
diff --git a/app/models/tournament.rb b/app/models/tournament.rb
index 0c06115..119fcca 100644
--- a/app/models/tournament.rb
+++ b/app/models/tournament.rb
@@ -8,6 +8,7 @@ class Tournament < ApplicationRecord
has_many :matches, dependent: :destroy
has_many :delegates, class_name: "TournamentDelegate"
has_many :mat_assignment_rules, dependent: :destroy
+ has_many :tournament_backups, dependent: :destroy
validates :date, :name, :tournament_type, :address, :director, :director_email , presence: true
@@ -227,5 +228,9 @@ class Tournament < ApplicationRecord
end
end
end
+
+ def create_backup()
+ TournamentBackupService.new(self, "Manual backup").create_backup
+ end
end
\ No newline at end of file
diff --git a/app/models/tournament_backup.rb b/app/models/tournament_backup.rb
new file mode 100644
index 0000000..72098a2
--- /dev/null
+++ b/app/models/tournament_backup.rb
@@ -0,0 +1,5 @@
+class TournamentBackup < ApplicationRecord
+ belongs_to :tournament
+
+ validates :backup_data, presence: true
+end
\ No newline at end of file
diff --git a/app/services/tournament_services/tournament_backup_service.rb b/app/services/tournament_services/tournament_backup_service.rb
new file mode 100644
index 0000000..eb94fc9
--- /dev/null
+++ b/app/services/tournament_services/tournament_backup_service.rb
@@ -0,0 +1,63 @@
+class TournamentBackupService
+ def initialize(tournament, reason)
+ @tournament = tournament
+ @reason = reason
+ end
+
+ def create_backup
+ if Rails.env.production?
+ self.delay(:job_owner_id => @tournament.id, :job_owner_type => "Create a backup").create_backup_raw
+ else
+ self.create_backup_raw
+ end
+ end
+
+ def create_backup_raw
+ # Generate the JSON directly in Ruby and encode it
+ backup_data = Base64.encode64(generate_json.to_json)
+
+ begin
+ # Save the backup with encoded data
+ TournamentBackup.create!(tournament: @tournament, backup_data: backup_data, backup_reason: @reason)
+ Rails.logger.info("Backup created successfully for tournament ##{@tournament.id}")
+ rescue ActiveRecord::RecordInvalid => e
+ Rails.logger.error("Failed to save backup: #{e.message}")
+ end
+ end
+
+ private
+
+ def generate_json
+ {
+ tournament: {
+ attributes: @tournament.attributes,
+ schools: @tournament.schools.map(&:attributes),
+ weights: @tournament.weights.map(&:attributes),
+ mats: @tournament.mats.map(&:attributes),
+ mat_assignment_rules: @tournament.mat_assignment_rules.map do |rule|
+ rule.attributes.merge(
+ mat: Mat.find_by(id: rule.mat_id)&.attributes.slice("name"),
+ weight_classes: rule.weight_classes.map do |weight_id|
+ Weight.find_by(id: weight_id)&.max
+ end
+ )
+ end,
+ wrestlers: @tournament.wrestlers.map do |wrestler|
+ wrestler.attributes.merge(
+ school: wrestler.school&.attributes,
+ weight: wrestler.weight&.attributes
+ )
+ end,
+ matches: @tournament.matches.sort_by(&:bout_number).map do |match|
+ match.attributes.merge(
+ w1_name: Wrestler.find_by(id: match.w1)&.name,
+ w2_name: Wrestler.find_by(id: match.w2)&.name,
+ winner_name: Wrestler.find_by(id: match.winner_id)&.name,
+ weight: Weight.find_by(id: match.weight_id)&.attributes,
+ mat: Mat.find_by(id: match.mat_id)&.attributes
+ )
+ end
+ }
+ }
+ end
+end
diff --git a/app/services/tournament_services/wrestlingdev_importer.rb b/app/services/tournament_services/wrestlingdev_importer.rb
index e5d06cd..02d963b 100644
--- a/app/services/tournament_services/wrestlingdev_importer.rb
+++ b/app/services/tournament_services/wrestlingdev_importer.rb
@@ -1,11 +1,11 @@
class WrestlingdevImporter
- ##### Note, the json contains id's for each row in the tables as well as it's associations
+ ##### Note, the json contains id's for each row in the tables as well as its associations
##### this ignores those ids and uses this tournament id and then looks up associations based on name
##### and this tournament id
- def initialize(tournament, import_json)
+ def initialize(tournament, backup)
@tournament = tournament
- @import_data = JSON.parse(import_json)
+ @import_data = JSON.parse(Base64.decode64(backup.backup_data))
end
def import
@@ -26,6 +26,7 @@ class WrestlingdevImporter
end
def destroy_all
+ @tournament.mat_assignment_rules.destroy_all
@tournament.mats.destroy_all
@tournament.matches.destroy_all
@tournament.schools.each do |school|
@@ -42,6 +43,8 @@ class WrestlingdevImporter
parse_mats(@import_data["tournament"]["mats"])
parse_wrestlers(@import_data["tournament"]["wrestlers"])
parse_matches(@import_data["tournament"]["matches"])
+ puts "Parsing mat assignment rules"
+ parse_mat_assignment_rules(@import_data["tournament"]["mat_assignment_rules"])
end
def parse_tournament(attributes)
@@ -70,6 +73,34 @@ class WrestlingdevImporter
end
end
+ def parse_mat_assignment_rules(mat_assignment_rules)
+ mat_assignment_rules.each do |rule_attributes|
+ mat_name = rule_attributes.dig("mat", "name")
+ mat = Mat.find_by(name: mat_name, tournament_id: @tournament.id)
+
+ # Map max values of weight_classes to their new IDs
+ new_weight_classes = rule_attributes["weight_classes"].map do |max_value|
+ Weight.find_by(max: max_value, tournament_id: @tournament.id)&.id
+ end.compact
+
+ # Extract bracket_positions and rounds
+ bracket_positions = rule_attributes["bracket_positions"]
+ rounds = rule_attributes["rounds"]
+
+ rule_attributes.except!("id", "mat", "tournament_id", "weight_classes")
+
+ MatAssignmentRule.create(
+ rule_attributes.merge(
+ tournament_id: @tournament.id,
+ mat_id: mat&.id,
+ weight_classes: new_weight_classes,
+ bracket_positions: bracket_positions,
+ rounds: rounds
+ )
+ )
+ end
+ end
+
def parse_wrestlers(wrestlers)
wrestlers.each do |wrestler_attributes|
school = School.find_by(name: wrestler_attributes["school"]["name"], tournament_id: @tournament.id)
diff --git a/app/views/layouts/_tournament-navbar.html.erb b/app/views/layouts/_tournament-navbar.html.erb
index 1d2a6d6..6c1abd6 100644
--- a/app/views/layouts/_tournament-navbar.html.erb
+++ b/app/views/layouts/_tournament-navbar.html.erb
@@ -33,6 +33,7 @@
<%= 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 'Manage Backups', tournament_tournament_backups_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" %>
diff --git a/app/views/tournament_backups/index.html.erb b/app/views/tournament_backups/index.html.erb
new file mode 100644
index 0000000..c3fb464
--- /dev/null
+++ b/app/views/tournament_backups/index.html.erb
@@ -0,0 +1,38 @@
+Tournament Backups
+You can click on the timestamp to view the backup text. You can manually store this anywhere you'd like and then paste it into the manual import field below.
+Doing this is risky, if the text is formatted incorrectly (like Microsoft Word changing the quotation marks), it will not import properly
+and will also delete all of your current data. It's best to use the create backup function.
+
+
+
+
+ | Backup Created At |
+ Backup Reason |
+ <%= link_to ' Create New Backup', tournament_tournament_backups_path(@tournament), method: :post, class: 'fas fa-plus'%> |
+
+
+
+ <% @tournament_backups.each do |backup| %>
+
+ |
+ <%= link_to backup.created_at.strftime('%Y-%m-%d %H:%M:%S'), tournament_tournament_backup_path(@tournament, backup, print: true), target: '_blank', class: 'text-decoration-none' %>
+ |
+ <%= backup.backup_reason.presence || 'No reason provided' %> |
+
+ <%= link_to '', restore_tournament_tournament_backup_path(@tournament, backup), method: :post, data: { confirm: "This will restore the backup from #{backup.created_at.strftime('%Y-%m-%d %H:%M:%S')}. It will delete all current data from the tournament in order to restore the backup." }, class: 'fas fa-undo-alt text-warning', title: 'Restore Backup' %>
+ <%= link_to '', tournament_tournament_backup_path(@tournament, backup), method: :delete, data: { confirm: 'Are you sure you want to delete this backup?' }, class: 'fas fa-trash-alt', title: 'Delete Backup' %>
+ |
+
+ <% end %>
+
+
+
+Import Manual Backup
+Paste the backup text here. Note, if this is formatted wrong, you'll need to restore a backup from above to fix it and you'll see an error in your background jobs.
+<%= form_for(:tournament, url: import_manual_tournament_tournament_backups_path(@tournament)) do |f| %>
+
+ <%= f.label 'Import text' %>
+ <%= f.text_area :import_text, cols: "30", rows: "20" %>
+
+ <%= submit_tag "Import", class: "btn btn-success", data: { confirm: 'Are you sure? This will delete everything for the current tournament and restore it with the backup text pasted below.' } %>
+<% end %>
diff --git a/app/views/tournament_backups/show.html.erb b/app/views/tournament_backups/show.html.erb
new file mode 100644
index 0000000..4c491e1
--- /dev/null
+++ b/app/views/tournament_backups/show.html.erb
@@ -0,0 +1 @@
+<%= JSON.pretty_generate(JSON.parse(Base64.decode64(@tournament_backup.backup_data))) %>
\ No newline at end of file
diff --git a/app/views/tournaments/_import_form.html.erb b/app/views/tournaments/_import_form.html.erb
deleted file mode 100644
index 954b8c6..0000000
--- a/app/views/tournaments/_import_form.html.erb
+++ /dev/null
@@ -1,11 +0,0 @@
-<% if can? :manage, @tournament %>
-
- Import Data
- <%= form_for(Tournament.new, url: import_url(@tournament)) do |f| %>
-
- <%= f.label 'Import text' %>
- <%= f.text_area :import_text, cols: "30", rows: "20" %>
-
- <%= submit_tag "Import", :class=>"btn btn-success", data: { confirm: 'Are you sure? This will delete everything for the current tournament and restore it with the backup text pasted below.' }%>
- <% end %>
-<% end %>
\ No newline at end of file
diff --git a/app/views/tournaments/show.html.erb b/app/views/tournaments/show.html.erb
index 0a742a3..3acaed0 100644
--- a/app/views/tournaments/show.html.erb
+++ b/app/views/tournaments/show.html.erb
@@ -119,7 +119,7 @@
<% end %>
<% if can? :manage, @tournament %>
-
+
Background Jobs
This is a list of queued or running background jobs. Match generation, bracket advancement, team score calculation, etc.
<% end %>
-
-
-<%= render 'import_form' %>
-
-
-
+
diff --git a/config/routes.rb b/config/routes.rb
index 91fc00c..a68edd0 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -11,6 +11,11 @@ Wrestling::Application.routes.draw do
member do
post :reset_bout_board
end
+ resources :tournament_backups, only: [:index, :show, :destroy] do
+ post :create, on: :collection
+ post :restore, on: :member
+ post :import_manual, on: :collection
+ end
end
resources :schools
@@ -57,8 +62,6 @@ Wrestling::Application.routes.draw do
delete 'tournaments/:id/:teampointadjust/remove_teampointadjust' => 'tournaments#remove_teampointadjust'
get 'tournaments/:id/error' => 'tournaments#error'
post "/tournaments/:id/swap" => "tournaments#swap", :as => :swap_wrestlers
- get 'tournaments/:id/export' => "tournaments#export"
- post "/tournaments/:id/import" => "tournaments#import", :as => :import
get "/tournaments/:id/brackets" => "tournaments#show"
put "/tournaments/:id/calculate_team_scores", :to => "tournaments#calculate_team_scores"
diff --git a/db/migrate/20241224132705_create_tournament_backups.rb b/db/migrate/20241224132705_create_tournament_backups.rb
new file mode 100644
index 0000000..a0bbe21
--- /dev/null
+++ b/db/migrate/20241224132705_create_tournament_backups.rb
@@ -0,0 +1,16 @@
+class CreateTournamentBackups < ActiveRecord::Migration[7.0]
+ def up
+ create_table :tournament_backups do |t|
+ t.integer :tournament_id, null: false, foreign_key: true
+ t.text :backup_data, null: false, limit: 4294967295 # Use LONGTEXT for large backups
+ t.string :backup_reason
+ t.timestamps
+ end
+ end
+
+ def down
+ # Drop the table
+ drop_table :tournament_backups
+ end
+end
+
diff --git a/db/schema.rb b/db/schema.rb
index ee2d16b..aca546e 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_10_27_203209) do
+ActiveRecord::Schema[7.2].define(version: 2024_12_24_132705) do
create_table "delayed_jobs", force: :cascade do |t|
t.integer "priority", default: 0, null: false
t.integer "attempts", default: 0, null: false
@@ -99,6 +99,14 @@ ActiveRecord::Schema[7.1].define(version: 2024_10_27_203209) do
t.index ["wrestler_id"], name: "index_teampointadjusts_on_wrestler_id"
end
+ create_table "tournament_backups", force: :cascade do |t|
+ t.integer "tournament_id", null: false
+ t.text "backup_data", limit: 4294967295, null: false
+ t.string "backup_reason"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ end
+
create_table "tournament_delegates", force: :cascade do |t|
t.integer "user_id"
t.integer "tournament_id"
@@ -166,5 +174,4 @@ ActiveRecord::Schema[7.1].define(version: 2024_10_27_203209) do
t.index ["school_id"], name: "index_wrestlers_on_school_id"
t.index ["weight_id"], name: "index_wrestlers_on_weight_id"
end
-
end
diff --git a/test/controllers/tournament_backups_controller_test.rb b/test/controllers/tournament_backups_controller_test.rb
new file mode 100644
index 0000000..0c2f04c
--- /dev/null
+++ b/test/controllers/tournament_backups_controller_test.rb
@@ -0,0 +1,220 @@
+require "test_helper"
+
+class TournamentBackupsControllerTest < ActionController::TestCase
+ include Devise::Test::ControllerHelpers
+
+ setup do
+ @tournament = Tournament.find(1)
+ TournamentBackupService.new(@tournament, 'Manual backup').create_backup
+ @backup = @tournament.tournament_backups.first
+ end
+
+ def sign_in_owner
+ sign_in users(:one)
+ end
+
+ def sign_in_non_owner
+ sign_in users(:two)
+ end
+
+ def sign_in_delegate
+ sign_in users(:three)
+ end
+
+ def sign_in_school_delegate
+ sign_in users(:four)
+ end
+
+ def success
+ assert_response :success
+ end
+
+ def redirect
+ assert_redirected_to '/static_pages/not_allowed'
+ end
+
+ # Index endpoint tests
+ test "logged in tournament owner can access index" do
+ sign_in_owner
+ get :index, params: { tournament_id: @tournament.id }
+ assert_response :success
+ end
+
+ test "logged in delegate can access index" do
+ sign_in_delegate
+ get :index, params: { tournament_id: @tournament.id }
+ assert_response :success
+ end
+
+ test "non-tournament owner cannot access index" do
+ sign_in_non_owner
+ get :index, params: { tournament_id: @tournament.id }
+ redirect
+ end
+
+ test "school delegate cannot access index" do
+ sign_in_school_delegate
+ get :index, params: { tournament_id: @tournament.id }
+ redirect
+ end
+
+ # Show endpoint tests
+ test "tournament owner can view a backup" do
+ sign_in_owner
+ get :show, params: { tournament_id: @tournament.id, id: @backup.id }
+ assert_response :success
+ end
+
+ test "delegate can view a backup" do
+ sign_in_delegate
+ get :show, params: { tournament_id: @tournament.id, id: @backup.id }
+ assert_response :success
+ end
+
+ test "non-tournament owner cannot view a backup" do
+ sign_in_non_owner
+ get :show, params: { tournament_id: @tournament.id, id: @backup.id }
+ redirect
+ end
+
+ test "school delegate cannot view a backup" do
+ sign_in_school_delegate
+ get :show, params: { tournament_id: @tournament.id, id: @backup.id }
+ redirect
+ end
+
+ # Destroy endpoint tests
+ test "tournament owner can delete a backup" do
+ sign_in_owner
+ assert_difference("TournamentBackup.count", -1) do
+ delete :destroy, params: { tournament_id: @tournament.id, id: @backup.id }
+ end
+ assert_redirected_to tournament_tournament_backups_path(@tournament)
+ end
+
+ test "delegate can delete a backup" do
+ sign_in_delegate
+ assert_difference("TournamentBackup.count", -1) do
+ delete :destroy, params: { tournament_id: @tournament.id, id: @backup.id }
+ end
+ assert_redirected_to tournament_tournament_backups_path(@tournament)
+ end
+
+ test "non-tournament owner cannot delete a backup" do
+ sign_in_non_owner
+ assert_no_difference("TournamentBackup.count") do
+ delete :destroy, params: { tournament_id: @tournament.id, id: @backup.id }
+ end
+ redirect
+ end
+
+ test "school delegate cannot delete a backup" do
+ sign_in_school_delegate
+ assert_no_difference("TournamentBackup.count") do
+ delete :destroy, params: { tournament_id: @tournament.id, id: @backup.id }
+ end
+ redirect
+ end
+
+ # Restore endpoint tests
+ test "tournament owner can restore a backup" do
+ sign_in_owner
+ post :restore, params: { tournament_id: @tournament.id, id: @backup.id }
+ assert_redirected_to tournament_path(@tournament)
+ end
+
+ test "delegate can restore a backup" do
+ sign_in_delegate
+ post :restore, params: { tournament_id: @tournament.id, id: @backup.id }
+ assert_redirected_to tournament_path(@tournament)
+ end
+
+ test "non-tournament owner cannot restore a backup" do
+ sign_in_non_owner
+ post :restore, params: { tournament_id: @tournament.id, id: @backup.id }
+ redirect
+ end
+
+ test "school delegate cannot restore a backup" do
+ sign_in_school_delegate
+ post :restore, params: { tournament_id: @tournament.id, id: @backup.id }
+ redirect
+ end
+
+ # Import manual tests
+ test "tournament owner can manually import a backup" do
+ sign_in_owner
+ post :import_manual, params: { tournament_id: @tournament.id, tournament: { import_text: Base64.decode64(@backup.backup_data) } }
+ assert_redirected_to tournament_path(@tournament)
+ end
+
+ test "delegate can manually import a backup" do
+ sign_in_delegate
+ post :import_manual, params: { tournament_id: @tournament.id, tournament: { import_text: Base64.decode64(@backup.backup_data) } }
+ assert_redirected_to tournament_path(@tournament)
+ end
+
+ test "non-tournament owner cannot manually import a backup" do
+ sign_in_non_owner
+ post :import_manual, params: { tournament_id: @tournament.id, tournament: { import_text: Base64.decode64(@backup.backup_data) } }
+ redirect
+ end
+
+ test "school delegate cannot manually import a backup" do
+ sign_in_school_delegate
+ post :import_manual, params: { tournament_id: @tournament.id, tournament: { import_text: Base64.decode64(@backup.backup_data) } }
+ redirect
+ end
+
+ test "index shows empty list when no backups exist" do
+ @tournament.tournament_backups.destroy_all
+ sign_in_owner
+ get :index, params: { tournament_id: @tournament.id }
+ assert_response :success
+ assert_select 'tbody tr', 0 # Ensure no rows are rendered in the table
+ end
+
+ test "show action for non-existent backup" do
+ sign_in_owner
+ assert_raises(ActiveRecord::RecordNotFound) do
+ get :show, params: { tournament_id: @tournament.id, id: 9999 } # Use a non-existent backup ID
+ end
+ end
+
+ test "destroy action for non-existent backup" do
+ sign_in_owner
+ assert_no_difference("TournamentBackup.count") do
+ assert_raises(ActiveRecord::RecordNotFound) do
+ delete :destroy, params: { tournament_id: @tournament.id, id: 9999 } # Use a non-existent backup ID
+ end
+ end
+ end
+
+ test "restore action for non-existent backup" do
+ sign_in_owner
+ assert_raises(ActiveRecord::RecordNotFound) do
+ post :restore, params: { tournament_id: @tournament.id, id: 9999 } # Use a non-existent backup ID
+ end
+ end
+
+ test "manual import with blank input" do
+ sign_in_owner
+ post :import_manual, params: { tournament_id: @tournament.id, tournament: { import_text: '' } }
+ assert_redirected_to tournament_tournament_backups_path(@tournament)
+ assert_equal 'Import text cannot be blank.', flash[:alert]
+ end
+
+ test "manual import restores associations" do
+ schools_count = @tournament.schools.count
+ wrestlers_count = @tournament.wrestlers.count
+ matches_count = @tournament.matches.count
+ sign_in_owner
+ valid_backup_data = Base64.decode64(@backup.backup_data)
+ post :import_manual, params: { tournament_id: @tournament.id, tournament: { import_text: valid_backup_data } }
+
+ @tournament.reload
+ assert_equal schools_count, @tournament.schools.count
+ assert_equal wrestlers_count, @tournament.wrestlers.count
+ assert_equal matches_count, @tournament.matches.count
+ end
+end
diff --git a/test/fixtures/tournament_backups.yml b/test/fixtures/tournament_backups.yml
new file mode 100644
index 0000000..1f0df1d
--- /dev/null
+++ b/test/fixtures/tournament_backups.yml
@@ -0,0 +1,11 @@
+# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
+
+# This model initially had no columns defined. If you add columns to the
+# model remove the "{}" from the fixture names and add the columns immediately
+# below each fixture, per the syntax in the comments below
+#
+# one: {}
+# column: value
+#
+# two: {}
+# column: value
diff --git a/test/models/tournament_backup_test.rb b/test/models/tournament_backup_test.rb
new file mode 100644
index 0000000..91fdf35
--- /dev/null
+++ b/test/models/tournament_backup_test.rb
@@ -0,0 +1,7 @@
+require "test_helper"
+
+class TournamentBackupTest < ActiveSupport::TestCase
+ # test "the truth" do
+ # assert true
+ # end
+end