From 0d30886168ea2275f265d4a84b365f21a0e609b9 Mon Sep 17 00:00:00 2001 From: Malaber Date: Tue, 4 Mar 2025 21:59:57 +0100 Subject: [PATCH 1/9] REVERT ME run only focused tests --- Gemfile.lock | 1 + spec/rails_helper.rb | 1 + 2 files changed, 2 insertions(+) diff --git a/Gemfile.lock b/Gemfile.lock index d40cadd..0779004 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -328,6 +328,7 @@ PLATFORMS aarch64-linux-musl arm64-darwin-22 arm64-darwin-23 + arm64-darwin-24 x86_64-linux DEPENDENCIES diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 18608f5..15c095d 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -39,6 +39,7 @@ RSpec.configure do |config| config.fixture_path = "#{::Rails.root}/spec/fixtures" # Run only focused tests + # TODO REVERT ME config.filter_run_when_matching :focus # If you're not using ActiveRecord, or you'd prefer not to run each of your From 3c1168212e4ab8338f5d2cd8260bb8e6919d7dc8 Mon Sep 17 00:00:00 2001 From: Tobias Huber Date: Sat, 8 Mar 2025 17:08:48 +0100 Subject: [PATCH 2/9] add timer_end to tournaments model --- app/controllers/tournaments_controller.rb | 42 ++++++++++++- config/routes.rb | 4 ++ .../tournaments_controller_spec.rb | 59 +++++++++++++++++++ 3 files changed, 102 insertions(+), 3 deletions(-) diff --git a/app/controllers/tournaments_controller.rb b/app/controllers/tournaments_controller.rb index a40c6bf..19cb4ce 100644 --- a/app/controllers/tournaments_controller.rb +++ b/app/controllers/tournaments_controller.rb @@ -1,11 +1,12 @@ # frozen_string_literal: true class TournamentsController < ApplicationController - before_action :set_tournament, only: %i[show update destroy] - before_action :authenticate_user!, only: %i[create update destroy] - before_action -> { require_owner! @tournament.owner }, only: %i[update destroy] + before_action :set_tournament, only: %i[show update destroy set_timer_end, timer_end] + before_action :authenticate_user!, only: %i[create update destroy set_timer_end] + before_action -> { require_owner! @tournament.owner }, only: %i[update destroy set_timer_end] before_action :validate_create_params, only: %i[create] before_action :validate_update_params, only: %i[update] + before_action :validate_set_timer_end_params, only: %i[set_timer_end] rescue_from ActiveRecord::RecordNotFound, with: :render_not_found_error # GET /tournaments @@ -93,8 +94,27 @@ class TournamentsController < ApplicationController @tournament.destroy end + # GET /tournaments/:id/timer_end + def timer_end + render json: { timer_end: @tournament.timer_end } + end + + # PATCH /tournaments/:id/set_timer_end + def set_timer_end + if @tournament.update(timer_end_params) + render json: @tournament + else + render json: @tournament.errors, status: :unprocessable_entity + end + end + + private + def timer_end_params + { timer_end: params[:timer_end] } + end + def organize_teams_in_groups(teams) # each team gets put into a array of teams depending on the group specified in team[:group] teams.group_by { |team| team['group'] }.values.map do |group| @@ -158,3 +178,19 @@ class TournamentsController < ApplicationController }, status: :unprocessable_entity end end + +def validate_set_timer_end_params + timer_end = params[:timer_end] + return render json: { error: 'Timer end is required' }, status: :unprocessable_entity unless timer_end.present? + + begin + parsed_time = Time.zone.parse(timer_end) + if parsed_time.nil? + render json: { error: 'Invalid datetime format' }, status: :unprocessable_entity + elsif !parsed_time.future? + render json: { error: 'Timer end must be in the future' }, status: :unprocessable_entity + end + rescue ArgumentError + render json: { error: 'Invalid datetime format' }, status: :unprocessable_entity + end +end \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index 101e869..9a6cf02 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -14,6 +14,10 @@ Rails.application.routes.draw do resources :tournaments do resources :statistics, only: %i[index] resources :matches, only: %i[index] + member do + get :timer_end + patch :set_timer_end + end end resources :match_scores, only: %i[show update] resources :groups, only: %i[show] diff --git a/spec/controllers/tournaments_controller_spec.rb b/spec/controllers/tournaments_controller_spec.rb index ae8f469..e0c4f82 100644 --- a/spec/controllers/tournaments_controller_spec.rb +++ b/spec/controllers/tournaments_controller_spec.rb @@ -477,4 +477,63 @@ RSpec.describe TournamentsController, type: :controller do end end end + + describe '#validate_set_timer_end_params' do + let(:tournament) { create(:tournament) } + + before do + apply_authentication_headers_for tournament.owner + @request.env['HTTP_ACCEPT'] = 'application/json' + end + + context 'when timer_end is missing' do + it 'returns unprocessable entity' do + patch :set_timer_end, params: { id: tournament.id } + expect(response).to have_http_status(:unprocessable_entity) + expect(JSON.parse(response.body)).to include('error' => 'Timer end is required') + end + end + + context 'when timer_end is invalid datetime' do + it 'returns unprocessable entity' do + patch :set_timer_end, params: { id: tournament.id, timer_end: 'invalid' } + expect(response).to have_http_status(:unprocessable_entity) + expect(JSON.parse(response.body)).to include('error' => 'Invalid datetime format') + end + end + + context 'when timer_end is in the past' do + it 'returns unprocessable entity' do + patch :set_timer_end, params: { id: tournament.id, timer_end: 1.day.ago } + expect(response).to have_http_status(:unprocessable_entity) + expect(JSON.parse(response.body)).to include('error' => 'Timer end must be in the future') + end + end + + context 'when timer_end is valid' do + it 'passes validation' do + patch :set_timer_end, params: { id: tournament.id, timer_end: 1.day.from_now } + expect(response).to have_http_status(:ok) + end + end + end + + describe 'GET #timer_end', focus:true do + let(:tournament) { create(:tournament) } + + before do + @request.env['HTTP_ACCEPT'] = 'application/json' + end + + it 'returns success response' do + get :timer_end, params: { id: tournament.to_param } + expect(response).to have_http_status(:ok) + end + + it 'returns timer_end value' do + tournament.update(timer_end: Time.zone.now + 1.day) + get :timer_end, params: { id: tournament.to_param } + expect(JSON.parse(response.body)['timer_end']).to eq(tournament.timer_end.as_json) + end + end end From 514bbb553b63be0eadf81743f1cc32ce1715923b Mon Sep 17 00:00:00 2001 From: Malaber Date: Sun, 9 Mar 2025 21:27:38 +0100 Subject: [PATCH 3/9] Add migration for timer --- db/migrate/20250309202658_add_timer_end_to_tournaments.rb | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 db/migrate/20250309202658_add_timer_end_to_tournaments.rb diff --git a/db/migrate/20250309202658_add_timer_end_to_tournaments.rb b/db/migrate/20250309202658_add_timer_end_to_tournaments.rb new file mode 100644 index 0000000..1762bec --- /dev/null +++ b/db/migrate/20250309202658_add_timer_end_to_tournaments.rb @@ -0,0 +1,5 @@ +class AddTimerEndToTournaments < ActiveRecord::Migration[7.0] + def change + add_column :tournaments, :timer_end, :datetime + end +end From 7b9a454cb627992321ec4625089f964aa1b140f5 Mon Sep 17 00:00:00 2001 From: Malaber Date: Mon, 10 Mar 2025 11:49:51 +0100 Subject: [PATCH 4/9] Fix syntax in tournaments_controller.rb --- app/controllers/tournaments_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/tournaments_controller.rb b/app/controllers/tournaments_controller.rb index 19cb4ce..88c4de8 100644 --- a/app/controllers/tournaments_controller.rb +++ b/app/controllers/tournaments_controller.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class TournamentsController < ApplicationController - before_action :set_tournament, only: %i[show update destroy set_timer_end, timer_end] + before_action :set_tournament, only: %i[show update destroy set_timer_end timer_end] before_action :authenticate_user!, only: %i[create update destroy set_timer_end] before_action -> { require_owner! @tournament.owner }, only: %i[update destroy set_timer_end] before_action :validate_create_params, only: %i[create] From bdcc6e118276fc3eb56d7fd2511223167911f9d3 Mon Sep 17 00:00:00 2001 From: Malaber Date: Mon, 10 Mar 2025 11:50:25 +0100 Subject: [PATCH 5/9] Fix tests --- .../tournaments_controller_spec.rb | 38 ++++++++++--------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/spec/controllers/tournaments_controller_spec.rb b/spec/controllers/tournaments_controller_spec.rb index e0c4f82..9cd8bc5 100644 --- a/spec/controllers/tournaments_controller_spec.rb +++ b/spec/controllers/tournaments_controller_spec.rb @@ -478,17 +478,15 @@ RSpec.describe TournamentsController, type: :controller do end end - describe '#validate_set_timer_end_params' do - let(:tournament) { create(:tournament) } - - before do - apply_authentication_headers_for tournament.owner + describe 'PATCH #set_timer_end' do + before(:each) do + apply_authentication_headers_for @tournament.owner @request.env['HTTP_ACCEPT'] = 'application/json' end context 'when timer_end is missing' do it 'returns unprocessable entity' do - patch :set_timer_end, params: { id: tournament.id } + patch :set_timer_end, params: { id: @tournament.id } expect(response).to have_http_status(:unprocessable_entity) expect(JSON.parse(response.body)).to include('error' => 'Timer end is required') end @@ -496,7 +494,7 @@ RSpec.describe TournamentsController, type: :controller do context 'when timer_end is invalid datetime' do it 'returns unprocessable entity' do - patch :set_timer_end, params: { id: tournament.id, timer_end: 'invalid' } + patch :set_timer_end, params: { id: @tournament.id, timer_end: 'invalid' } expect(response).to have_http_status(:unprocessable_entity) expect(JSON.parse(response.body)).to include('error' => 'Invalid datetime format') end @@ -504,36 +502,42 @@ RSpec.describe TournamentsController, type: :controller do context 'when timer_end is in the past' do it 'returns unprocessable entity' do - patch :set_timer_end, params: { id: tournament.id, timer_end: 1.day.ago } + patch :set_timer_end, params: { id: @tournament.id, timer_end: 1.day.ago } expect(response).to have_http_status(:unprocessable_entity) expect(JSON.parse(response.body)).to include('error' => 'Timer end must be in the future') end end context 'when timer_end is valid' do - it 'passes validation' do - patch :set_timer_end, params: { id: tournament.id, timer_end: 1.day.from_now } + it 'updates the timer_end' do + valid_timer_end = 1.day.from_now.change(usec: 0) + patch :set_timer_end, params: { id: @tournament.id, timer_end: valid_timer_end } expect(response).to have_http_status(:ok) + expect(@tournament.reload.timer_end).to eq(valid_timer_end) end end end - describe 'GET #timer_end', focus:true do - let(:tournament) { create(:tournament) } - + describe 'GET #timer_end' do before do @request.env['HTTP_ACCEPT'] = 'application/json' end it 'returns success response' do - get :timer_end, params: { id: tournament.to_param } + get :timer_end, params: { id: @tournament.to_param } expect(response).to have_http_status(:ok) end it 'returns timer_end value' do - tournament.update(timer_end: Time.zone.now + 1.day) - get :timer_end, params: { id: tournament.to_param } - expect(JSON.parse(response.body)['timer_end']).to eq(tournament.timer_end.as_json) + @tournament.update(timer_end: Time.zone.now + 1.day) + get :timer_end, params: { id: @tournament.to_param } + expect(JSON.parse(response.body)['timer_end']).to eq(@tournament.timer_end.as_json) + end + + it 'returns nil if timer_end is not set' do + @tournament.update(timer_end: nil) + get :timer_end, params: { id: @tournament.to_param } + expect(JSON.parse(response.body)['timer_end']).to be_nil end end end From 4d1e2c5164739992f651277dc02f82cb589743ec Mon Sep 17 00:00:00 2001 From: Malaber Date: Mon, 10 Mar 2025 12:37:19 +0100 Subject: [PATCH 6/9] Add option to also pass seconds to timer_end --- app/controllers/tournaments_controller.rb | 38 +++++++--- .../tournaments_controller_spec.rb | 69 +++++++++++++------ 2 files changed, 76 insertions(+), 31 deletions(-) diff --git a/app/controllers/tournaments_controller.rb b/app/controllers/tournaments_controller.rb index 88c4de8..6cab8e9 100644 --- a/app/controllers/tournaments_controller.rb +++ b/app/controllers/tournaments_controller.rb @@ -181,16 +181,32 @@ end def validate_set_timer_end_params timer_end = params[:timer_end] - return render json: { error: 'Timer end is required' }, status: :unprocessable_entity unless timer_end.present? + timer_end_seconds = params[:timer_end_seconds] - begin - parsed_time = Time.zone.parse(timer_end) - if parsed_time.nil? - render json: { error: 'Invalid datetime format' }, status: :unprocessable_entity - elsif !parsed_time.future? - render json: { error: 'Timer end must be in the future' }, status: :unprocessable_entity - end - rescue ArgumentError - render json: { error: 'Invalid datetime format' }, status: :unprocessable_entity + # throw error if both timer_end and timer_end_seconds are present + if timer_end.present? && timer_end_seconds.present? + return render json: { error: 'Only one of timer_end or timer_end_seconds is allowed' }, status: :unprocessable_entity end -end \ No newline at end of file + + if timer_end_seconds.present? + begin + parsed_time = Time.zone.now + timer_end_seconds.to_i + params[:timer_end] = parsed_time + rescue ArgumentError + return render json: { error: 'Invalid seconds format' }, status: :unprocessable_entity + end + elsif timer_end.present? + begin + parsed_time = Time.zone.parse(timer_end) + if parsed_time.nil? + return render json: { error: 'Invalid datetime format' }, status: :unprocessable_entity + elsif !parsed_time.future? + return render json: { error: 'Timer end must be in the future' }, status: :unprocessable_entity + end + rescue ArgumentError + return render json: { error: 'Invalid datetime format' }, status: :unprocessable_entity + end + else + return render json: { error: 'Timer end is required' }, status: :unprocessable_entity + end +end diff --git a/spec/controllers/tournaments_controller_spec.rb b/spec/controllers/tournaments_controller_spec.rb index 9cd8bc5..5941ecd 100644 --- a/spec/controllers/tournaments_controller_spec.rb +++ b/spec/controllers/tournaments_controller_spec.rb @@ -483,37 +483,66 @@ RSpec.describe TournamentsController, type: :controller do apply_authentication_headers_for @tournament.owner @request.env['HTTP_ACCEPT'] = 'application/json' end + context 'timer_end' do + context 'when timer_end is missing' do + it 'returns unprocessable entity' do + patch :set_timer_end, params: { id: @tournament.id } + expect(response).to have_http_status(:unprocessable_entity) + expect(JSON.parse(response.body)).to include('error' => 'Timer end is required') + end + end - context 'when timer_end is missing' do - it 'returns unprocessable entity' do - patch :set_timer_end, params: { id: @tournament.id } - expect(response).to have_http_status(:unprocessable_entity) - expect(JSON.parse(response.body)).to include('error' => 'Timer end is required') + context 'when timer_end is invalid datetime' do + it 'returns unprocessable entity' do + patch :set_timer_end, params: { id: @tournament.id, timer_end: 'invalid' } + expect(response).to have_http_status(:unprocessable_entity) + expect(JSON.parse(response.body)).to include('error' => 'Invalid datetime format') + end + end + + context 'when timer_end is in the past' do + it 'returns unprocessable entity' do + patch :set_timer_end, params: { id: @tournament.id, timer_end: 1.day.ago } + expect(response).to have_http_status(:unprocessable_entity) + expect(JSON.parse(response.body)).to include('error' => 'Timer end must be in the future') + end + end + + context 'when timer_end is valid' do + it 'updates the timer_end' do + valid_timer_end = 1.day.from_now.change(usec: 0) + patch :set_timer_end, params: { id: @tournament.id, timer_end: valid_timer_end } + expect(response).to have_http_status(:ok) + expect(@tournament.reload.timer_end).to eq(valid_timer_end) + end end end - - context 'when timer_end is invalid datetime' do - it 'returns unprocessable entity' do - patch :set_timer_end, params: { id: @tournament.id, timer_end: 'invalid' } + context 'when timer_end_seconds is provided' do + it 'returns unprocessable entity for invalid seconds format' do + patch :set_timer_end, params: { id: @tournament.id, timer_end_seconds: 'invalid' } expect(response).to have_http_status(:unprocessable_entity) - expect(JSON.parse(response.body)).to include('error' => 'Invalid datetime format') + expect(JSON.parse(response.body)).to include('error' => 'Invalid seconds format') end - end - context 'when timer_end is in the past' do - it 'returns unprocessable entity' do - patch :set_timer_end, params: { id: @tournament.id, timer_end: 1.day.ago } + it 'returns unprocessable entity for negative seconds' do + patch :set_timer_end, params: { id: @tournament.id, timer_end_seconds: -3600 } expect(response).to have_http_status(:unprocessable_entity) expect(JSON.parse(response.body)).to include('error' => 'Timer end must be in the future') end - end - context 'when timer_end is valid' do - it 'updates the timer_end' do - valid_timer_end = 1.day.from_now.change(usec: 0) - patch :set_timer_end, params: { id: @tournament.id, timer_end: valid_timer_end } + it 'updates the timer_end with valid seconds' do + valid_timer_end_seconds = 3600 + expected_timer_end = (Time.zone.now + valid_timer_end_seconds).change(usec: 0) + patch :set_timer_end, params: { id: @tournament.id, timer_end_seconds: valid_timer_end_seconds } expect(response).to have_http_status(:ok) - expect(@tournament.reload.timer_end).to eq(valid_timer_end) + expect(@tournament.reload.timer_end.change(usec: 0)).to eq(expected_timer_end) + end + end + context 'when both timer_end and timer_end_seconds are provided' do + it 'returns unprocessable entity' do + patch :set_timer_end, params: { id: @tournament.id, timer_end: 1.day.from_now, timer_end_seconds: 3600 } + expect(response).to have_http_status(:unprocessable_entity) + expect(JSON.parse(response.body)).to include('error' => 'Only one of timer_end or timer_end_seconds is allowed') end end end From 16e65a3b0344eca945ced506130fc903b0e82190 Mon Sep 17 00:00:00 2001 From: Malaber Date: Mon, 10 Mar 2025 12:56:20 +0100 Subject: [PATCH 7/9] Fix missing checks --- app/controllers/tournaments_controller.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/controllers/tournaments_controller.rb b/app/controllers/tournaments_controller.rb index 6cab8e9..2e33843 100644 --- a/app/controllers/tournaments_controller.rb +++ b/app/controllers/tournaments_controller.rb @@ -190,11 +190,15 @@ def validate_set_timer_end_params if timer_end_seconds.present? begin - parsed_time = Time.zone.now + timer_end_seconds.to_i - params[:timer_end] = parsed_time + timer_end_seconds = Integer(timer_end_seconds) rescue ArgumentError return render json: { error: 'Invalid seconds format' }, status: :unprocessable_entity end + + return render json: { error: 'Timer end must be in the future' }, status: :unprocessable_entity if timer_end_seconds <= 0 + + parsed_time = Time.zone.now + timer_end_seconds + params[:timer_end] = parsed_time elsif timer_end.present? begin parsed_time = Time.zone.parse(timer_end) From f7dc8411f4f9489b39d0b3cdbffdff7d841f741d Mon Sep 17 00:00:00 2001 From: Malaber Date: Thu, 13 Mar 2025 15:56:01 +0100 Subject: [PATCH 8/9] Include timer_end in TournamentSerializer --- app/serializers/tournament_serializer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/serializers/tournament_serializer.rb b/app/serializers/tournament_serializer.rb index 25582e1..979a19a 100644 --- a/app/serializers/tournament_serializer.rb +++ b/app/serializers/tournament_serializer.rb @@ -2,7 +2,7 @@ class TournamentSerializer < SimpleTournamentSerializer attributes :description, :playoff_teams_amount, - :instant_finalists_amount, :intermediate_round_participants_amount + :instant_finalists_amount, :intermediate_round_participants_amount, :timer_end has_many :stages has_many :teams From 9f1807ab4bbe6d3a2f9e0162a569ec9824f68999 Mon Sep 17 00:00:00 2001 From: Malaber Date: Fri, 14 Mar 2025 12:47:06 +0100 Subject: [PATCH 9/9] Test timer_end is in show tournament --- spec/controllers/tournaments_controller_spec.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/spec/controllers/tournaments_controller_spec.rb b/spec/controllers/tournaments_controller_spec.rb index 5941ecd..f6574ee 100644 --- a/spec/controllers/tournaments_controller_spec.rb +++ b/spec/controllers/tournaments_controller_spec.rb @@ -100,7 +100,11 @@ RSpec.describe TournamentsController, type: :controller do it 'returns the requested tournament' do get :show, params: { id: @tournament.to_param } - expect(deserialize_response(response)[:id].to_i).to eq(@tournament.id) + json = deserialize_response(response) + expect(json[:id].to_i).to eq(@tournament.id) + expected_keys = %i[id name code public description playoff_teams_amount instant_finalists_amount intermediate_round_participants_amount timer_end owner_username stages teams] + expect(json.keys).to match_array(expected_keys) + expect(json).to eq(TournamentSerializer.new(@tournament).as_json) end context 'with simple=true parameter' do