Merge origin/master into follower sync
This commit is contained in:
commit
8789c2cb2e
|
|
@ -0,0 +1,69 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class BeamersController < ApplicationController
|
||||
before_action :set_tournament
|
||||
before_action :set_beamer, only: %i[show update destroy]
|
||||
before_action :authenticate_user!, only: %i[create update destroy]
|
||||
before_action -> { require_owner! @tournament.owner }, only: %i[create update destroy]
|
||||
rescue_from ActiveRecord::RecordNotFound, with: :render_not_found_error
|
||||
|
||||
# GET /tournaments/:tournament_id/beamers
|
||||
def index
|
||||
render json: @tournament.beamers, each_serializer: BeamerSerializer
|
||||
end
|
||||
|
||||
# GET /tournaments/:tournament_id/beamers/:id
|
||||
def show
|
||||
render json: @beamer
|
||||
end
|
||||
|
||||
# GET /tournaments/:tournament_id/beamers/by_name?name=beamername
|
||||
def by_name
|
||||
beamer = @tournament.beamers.find_by(name: params[:name])
|
||||
beamer ||= @tournament.beamers.find_by(name: Beamer::DEFAULT_NAME)
|
||||
if beamer
|
||||
render json: beamer
|
||||
else
|
||||
render json: { error: 'No beamer found' }, status: :not_found
|
||||
end
|
||||
end
|
||||
|
||||
# POST /tournaments/:tournament_id/beamers
|
||||
def create
|
||||
beamer = @tournament.beamers.new(beamer_params)
|
||||
if beamer.save
|
||||
render json: beamer, status: :created
|
||||
else
|
||||
render json: beamer.errors, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
# PATCH /tournaments/:tournament_id/beamers/:id
|
||||
def update
|
||||
if @beamer.update(beamer_params)
|
||||
render json: @beamer
|
||||
else
|
||||
render json: @beamer.errors, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
# DELETE /tournaments/:tournament_id/beamers/:id
|
||||
def destroy
|
||||
@beamer.destroy
|
||||
head :no_content
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_tournament
|
||||
@tournament = Tournament.find(params[:tournament_id])
|
||||
end
|
||||
|
||||
def set_beamer
|
||||
@beamer = @tournament.beamers.find(params[:id])
|
||||
end
|
||||
|
||||
def beamer_params
|
||||
params.permit(:name, :display_state, :qr_code_url, :logo_url)
|
||||
end
|
||||
end
|
||||
|
|
@ -127,7 +127,7 @@ class TournamentsController < ApplicationController
|
|||
|
||||
# GET /tournaments/:id/timer_end
|
||||
def timer_end
|
||||
render json: { timer_end: @tournament.timer_end }
|
||||
render json: { timer_end: serialize_timer_end(@tournament.timer_end) }
|
||||
end
|
||||
|
||||
# PATCH /tournaments/:id/set_timer_end
|
||||
|
|
@ -154,6 +154,10 @@ class TournamentsController < ApplicationController
|
|||
{ timer_end: params[:timer_end] }
|
||||
end
|
||||
|
||||
def serialize_timer_end(timer_end)
|
||||
timer_end&.iso8601
|
||||
end
|
||||
|
||||
def organize_teams_in_groups(teams)
|
||||
# each team gets put into an array of teams depending on the group specified in team[:group]
|
||||
teams.group_by { |team| team['group'] }.values.map do |group|
|
||||
|
|
@ -260,7 +264,6 @@ class TournamentsController < ApplicationController
|
|||
error: 'playoff_teams_amount, instant_finalists_amount and intermediate_round_participants_amount don\'t match'
|
||||
}, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
def sync_state_params
|
||||
params.require(:snapshot).permit!.to_h
|
||||
end
|
||||
|
|
@ -331,6 +334,8 @@ def validate_set_timer_end_params
|
|||
elsif !parsed_time.future?
|
||||
return render json: { error: 'Timer end must be in the future' }, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
params[:timer_end] = parsed_time
|
||||
rescue ArgumentError
|
||||
return render json: { error: 'Invalid datetime format' }, status: :unprocessable_entity
|
||||
end
|
||||
|
|
@ -338,3 +343,4 @@ def validate_set_timer_end_params
|
|||
return render json: { error: 'Timer end is required' }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Beamer < ApplicationRecord
|
||||
DEFAULT_NAME = 'default'
|
||||
DISPLAY_STATES = %w[
|
||||
matches_all matches_in_progress matches_upcoming matches_not_started
|
||||
matches_finished groups between_games during_game
|
||||
].freeze
|
||||
|
||||
belongs_to :tournament
|
||||
|
||||
validates :name, presence: true, uniqueness: { scope: :tournament_id }
|
||||
validates :display_state, presence: true, inclusion: { in: DISPLAY_STATES }
|
||||
|
||||
delegate :owner, to: :tournament
|
||||
end
|
||||
|
|
@ -6,6 +6,7 @@ class Tournament < ApplicationRecord
|
|||
belongs_to :user
|
||||
has_many :teams, dependent: :destroy
|
||||
has_many :stages, dependent: :destroy
|
||||
has_many :beamers, dependent: :destroy
|
||||
|
||||
validates :name, presence: true
|
||||
validates :code, presence: true, uniqueness: true
|
||||
|
|
@ -17,6 +18,7 @@ class Tournament < ApplicationRecord
|
|||
alias_attribute :owner, :user
|
||||
|
||||
after_initialize :generate_code
|
||||
after_create_commit :ensure_default_beamer!
|
||||
before_validation :clear_follower_sync_token_when_disabling_read_only_mode
|
||||
|
||||
def matches
|
||||
|
|
@ -69,6 +71,20 @@ class Tournament < ApplicationRecord
|
|||
errors.add(:base, 'sync_target_url and sync_auth_token must be configured together')
|
||||
end
|
||||
|
||||
def ensure_default_beamer!
|
||||
beamers.find_or_create_by!(name: Beamer::DEFAULT_NAME) do |beamer|
|
||||
beamer.display_state = 'matches_all'
|
||||
end
|
||||
end
|
||||
|
||||
def sync_configuration_is_complete
|
||||
return if sync_target_url.blank? && sync_auth_token.blank?
|
||||
return if sync_target_url.present? && sync_auth_token.present?
|
||||
return if read_only_mode? && sync_auth_token.present? && sync_target_url.blank?
|
||||
|
||||
errors.add(:base, 'sync_target_url and sync_auth_token must be configured together')
|
||||
end
|
||||
|
||||
def clear_follower_sync_token_when_disabling_read_only_mode
|
||||
return if read_only_mode?
|
||||
return if sync_target_url.present?
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class BeamerSerializer < ApplicationSerializer
|
||||
attributes :name, :display_state, :qr_code_url, :logo_url
|
||||
end
|
||||
|
|
@ -2,9 +2,13 @@
|
|||
|
||||
class TournamentSerializer < SimpleTournamentSerializer
|
||||
attributes :description, :playoff_teams_amount,
|
||||
:instant_finalists_amount, :intermediate_round_participants_amount, :timer_end
|
||||
:instant_finalists_amount, :intermediate_round_participants_amount
|
||||
has_many :stages
|
||||
|
||||
attribute :timer_end do
|
||||
object.timer_end&.iso8601
|
||||
end
|
||||
|
||||
attribute :owner_username do
|
||||
object.owner.username
|
||||
end
|
||||
|
|
|
|||
|
|
@ -17,6 +17,11 @@ Rails.application.routes.draw do
|
|||
resources :tournaments do
|
||||
resources :statistics, only: %i[index]
|
||||
resources :matches, only: %i[index]
|
||||
resources :beamers, only: %i[index show create update destroy] do
|
||||
collection do
|
||||
get :by_name
|
||||
end
|
||||
end
|
||||
member do
|
||||
get :timer_end
|
||||
patch :set_timer_end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateBeamers < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
create_table :beamers do |t|
|
||||
t.string :name, null: false
|
||||
t.string :display_state, null: false, default: 'matches_all'
|
||||
t.boolean :is_default, default: false
|
||||
t.string :qr_code_url
|
||||
t.string :logo_url
|
||||
t.belongs_to :tournament, index: true, foreign_key: { on_delete: :cascade }, null: false
|
||||
t.timestamps
|
||||
end
|
||||
add_index :beamers, %i[tournament_id name], unique: true
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class BackfillDefaultBeamersByName < ActiveRecord::Migration[7.0]
|
||||
DEFAULT_BEAMER_NAME = 'default'
|
||||
|
||||
class MigrationTournament < ApplicationRecord
|
||||
self.table_name = 'tournaments'
|
||||
end
|
||||
|
||||
class MigrationBeamer < ApplicationRecord
|
||||
self.table_name = 'beamers'
|
||||
end
|
||||
|
||||
def up
|
||||
MigrationTournament.find_each do |tournament|
|
||||
beamers = MigrationBeamer.where(tournament_id: tournament.id)
|
||||
next if beamers.exists?(name: DEFAULT_BEAMER_NAME)
|
||||
|
||||
legacy_default = beamers.find_by(is_default: true)
|
||||
|
||||
if legacy_default
|
||||
legacy_default.update_columns(name: DEFAULT_BEAMER_NAME)
|
||||
else
|
||||
MigrationBeamer.create!(
|
||||
tournament_id: tournament.id,
|
||||
name: DEFAULT_BEAMER_NAME,
|
||||
display_state: 'matches_all'
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
MigrationBeamer.where(name: DEFAULT_BEAMER_NAME).where.not(tournament_id: nil).find_each do |beamer|
|
||||
next unless MigrationBeamer.where(tournament_id: beamer.tournament_id).count == 1
|
||||
|
||||
beamer.delete
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,194 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe BeamersController, type: :controller do
|
||||
before do
|
||||
@tournament = create(:tournament)
|
||||
@owner = @tournament.owner
|
||||
@default_beamer = @tournament.beamers.find_by!(name: Beamer::DEFAULT_NAME)
|
||||
@beamer = create(:beamer, tournament: @tournament)
|
||||
end
|
||||
|
||||
describe 'GET #index' do
|
||||
it 'returns all beamers for the tournament' do
|
||||
get :index, params: { tournament_id: @tournament.to_param }
|
||||
expect(response).to be_successful
|
||||
body = deserialize_response response
|
||||
expect(body.length).to eq(2)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET #show' do
|
||||
it 'returns the beamer' do
|
||||
get :show, params: { tournament_id: @tournament.to_param, id: @beamer.to_param }
|
||||
expect(response).to be_successful
|
||||
body = deserialize_response response
|
||||
expect(body[:name]).to eq(@beamer.name)
|
||||
expect(body[:display_state]).to eq(@beamer.display_state)
|
||||
end
|
||||
|
||||
it 'returns 404 for nonexistent beamer' do
|
||||
get :show, params: { tournament_id: @tournament.to_param, id: 999_999 }
|
||||
expect(response).to have_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET #by_name' do
|
||||
it 'returns beamer by name' do
|
||||
get :by_name, params: { tournament_id: @tournament.to_param, name: @beamer.name }
|
||||
expect(response).to be_successful
|
||||
body = deserialize_response response
|
||||
expect(body[:name]).to eq(@beamer.name)
|
||||
end
|
||||
|
||||
it 'falls back to default beamer when name not found' do
|
||||
get :by_name, params: { tournament_id: @tournament.to_param, name: 'nonexistent' }
|
||||
expect(response).to be_successful
|
||||
body = deserialize_response response
|
||||
expect(body[:name]).to eq(@default_beamer.name)
|
||||
end
|
||||
|
||||
it 'falls back to default beamer when name param is missing' do
|
||||
get :by_name, params: { tournament_id: @tournament.to_param }
|
||||
expect(response).to be_successful
|
||||
body = deserialize_response response
|
||||
expect(body[:name]).to eq(Beamer::DEFAULT_NAME)
|
||||
end
|
||||
|
||||
it 'returns 404 when all beamers were deleted' do
|
||||
empty_tournament = create(:tournament)
|
||||
empty_tournament.beamers.destroy_all
|
||||
get :by_name, params: { tournament_id: empty_tournament.to_param, name: 'anything' }
|
||||
expect(response).to have_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST #create' do
|
||||
let(:valid_params) do
|
||||
{ name: 'new-beamer', display_state: 'groups' }
|
||||
end
|
||||
|
||||
context 'as owner' do
|
||||
before(:each) { apply_authentication_headers_for @owner }
|
||||
|
||||
it 'creates a new beamer' do
|
||||
expect {
|
||||
post :create, params: { tournament_id: @tournament.to_param }.merge(valid_params)
|
||||
}.to change(@tournament.beamers, :count).by(1)
|
||||
expect(response).to have_http_status(:created)
|
||||
end
|
||||
|
||||
it 'returns the created beamer' do
|
||||
post :create, params: { tournament_id: @tournament.to_param }.merge(valid_params)
|
||||
body = deserialize_response response
|
||||
expect(body[:name]).to eq('new-beamer')
|
||||
expect(body[:display_state]).to eq('groups')
|
||||
end
|
||||
|
||||
it 'rejects invalid display_state' do
|
||||
post :create, params: {
|
||||
tournament_id: @tournament.to_param, name: 'bad', display_state: 'invalid'
|
||||
}
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
end
|
||||
|
||||
it 'rejects duplicate name in same tournament' do
|
||||
post :create, params: {
|
||||
tournament_id: @tournament.to_param, name: @beamer.name, display_state: 'matches_all'
|
||||
}
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
end
|
||||
end
|
||||
|
||||
context 'as another user' do
|
||||
before(:each) { apply_authentication_headers_for create(:user) }
|
||||
|
||||
it 'returns forbidden' do
|
||||
post :create, params: { tournament_id: @tournament.to_param }.merge(valid_params)
|
||||
expect(response).to have_http_status(:forbidden)
|
||||
end
|
||||
end
|
||||
|
||||
context 'unauthenticated' do
|
||||
it 'returns unauthorized' do
|
||||
post :create, params: { tournament_id: @tournament.to_param }.merge(valid_params)
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PATCH #update' do
|
||||
context 'as owner' do
|
||||
before(:each) { apply_authentication_headers_for @owner }
|
||||
|
||||
it 'updates the display_state' do
|
||||
patch :update, params: {
|
||||
tournament_id: @tournament.to_param, id: @beamer.to_param,
|
||||
display_state: 'during_game'
|
||||
}
|
||||
expect(response).to be_successful
|
||||
body = deserialize_response response
|
||||
expect(body[:display_state]).to eq('during_game')
|
||||
expect(@beamer.reload.display_state).to eq('during_game')
|
||||
end
|
||||
|
||||
it 'updates optional fields' do
|
||||
patch :update, params: {
|
||||
tournament_id: @tournament.to_param, id: @beamer.to_param,
|
||||
qr_code_url: 'https://example.com/qr', logo_url: '/static/logo.png'
|
||||
}
|
||||
expect(response).to be_successful
|
||||
@beamer.reload
|
||||
expect(@beamer.qr_code_url).to eq('https://example.com/qr')
|
||||
expect(@beamer.logo_url).to eq('/static/logo.png')
|
||||
end
|
||||
|
||||
it 'rejects invalid display_state' do
|
||||
patch :update, params: {
|
||||
tournament_id: @tournament.to_param, id: @beamer.to_param,
|
||||
display_state: 'bogus'
|
||||
}
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
end
|
||||
end
|
||||
|
||||
context 'as another user' do
|
||||
before(:each) { apply_authentication_headers_for create(:user) }
|
||||
|
||||
it 'returns forbidden' do
|
||||
patch :update, params: {
|
||||
tournament_id: @tournament.to_param, id: @beamer.to_param,
|
||||
display_state: 'groups'
|
||||
}
|
||||
expect(response).to have_http_status(:forbidden)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'DELETE #destroy' do
|
||||
context 'as owner' do
|
||||
before(:each) { apply_authentication_headers_for @owner }
|
||||
|
||||
it 'deletes the beamer' do
|
||||
expect {
|
||||
delete :destroy, params: {
|
||||
tournament_id: @tournament.to_param, id: @beamer.to_param
|
||||
}
|
||||
}.to change(@tournament.beamers, :count).by(-1)
|
||||
expect(response).to have_http_status(:no_content)
|
||||
end
|
||||
end
|
||||
|
||||
context 'as another user' do
|
||||
before(:each) { apply_authentication_headers_for create(:user) }
|
||||
|
||||
it 'returns forbidden' do
|
||||
delete :destroy, params: {
|
||||
tournament_id: @tournament.to_param, id: @beamer.to_param
|
||||
}
|
||||
expect(response).to have_http_status(:forbidden)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -162,6 +162,34 @@ RSpec.describe TournamentsController, type: :controller do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'GET #timer_end' do
|
||||
it 'returns timer_end in ISO 8601 format' do
|
||||
timer_end = Time.utc(2026, 4, 13, 18, 0, 0)
|
||||
@tournament.update!(timer_end: timer_end)
|
||||
|
||||
get :timer_end, params: { id: @tournament.to_param }
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(deserialize_response(response)[:timer_end]).to eq(timer_end.iso8601)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PATCH #set_timer_end' do
|
||||
before(:each) do
|
||||
apply_authentication_headers_for @user
|
||||
end
|
||||
|
||||
it 'stores timezone-aware timer_end values without shifting them' do
|
||||
timer_end = '2030-04-13T18:00:00+02:00'
|
||||
|
||||
patch :set_timer_end, params: { id: @tournament.to_param, timer_end: timer_end }
|
||||
|
||||
expect(response).to be_successful
|
||||
expect(@tournament.reload.timer_end.iso8601).to eq(Time.iso8601(timer_end).utc.iso8601)
|
||||
expect(deserialize_response(response)[:timer_end]).to eq(@tournament.timer_end.iso8601)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST #create' do
|
||||
let(:create_playoff_tournament_data) do
|
||||
{
|
||||
|
|
@ -250,6 +278,14 @@ RSpec.describe TournamentsController, type: :controller do
|
|||
expect(included_teams).to match_array(@teams)
|
||||
end
|
||||
|
||||
it 'creates a default beamer automatically' do
|
||||
post :create, params: create_playoff_tournament_data
|
||||
body = deserialize_response response
|
||||
tournament = Tournament.find(body[:id])
|
||||
|
||||
expect(tournament.beamers.pluck(:name)).to include(Beamer::DEFAULT_NAME)
|
||||
end
|
||||
|
||||
context 'with parameter group_stage=true' do
|
||||
before do
|
||||
post :create, params: create_group_tournament_data
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :beamer do
|
||||
sequence(:name) { |n| "beamer-#{n}" }
|
||||
display_state { 'matches_all' }
|
||||
tournament
|
||||
|
||||
factory :default_beamer do
|
||||
name { Beamer::DEFAULT_NAME }
|
||||
|
||||
initialize_with do
|
||||
tournament.beamers.find_or_initialize_by(name: name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Beamer, type: :model do
|
||||
describe 'validation' do
|
||||
it { should validate_presence_of :name }
|
||||
it { should validate_presence_of :display_state }
|
||||
|
||||
it 'validates uniqueness of name scoped to tournament' do
|
||||
beamer = create(:beamer)
|
||||
duplicate = build(:beamer, name: beamer.name, tournament: beamer.tournament)
|
||||
expect(duplicate).not_to be_valid
|
||||
end
|
||||
|
||||
it 'allows same name in different tournaments' do
|
||||
beamer = create(:beamer, name: 'projector')
|
||||
other = build(:beamer, name: 'projector', tournament: create(:tournament))
|
||||
expect(other).to be_valid
|
||||
end
|
||||
|
||||
it 'validates display_state inclusion' do
|
||||
beamer = build(:beamer, display_state: 'invalid_state')
|
||||
expect(beamer).not_to be_valid
|
||||
end
|
||||
|
||||
Beamer::DISPLAY_STATES.each do |state|
|
||||
it "accepts display_state '#{state}'" do
|
||||
beamer = build(:beamer, display_state: state)
|
||||
expect(beamer).to be_valid
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'association' do
|
||||
it { should belong_to :tournament }
|
||||
end
|
||||
|
||||
describe '#owner' do
|
||||
it 'delegates to tournament' do
|
||||
beamer = create(:beamer)
|
||||
expect(beamer.owner).to eq(beamer.tournament.owner)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -29,6 +29,15 @@ RSpec.describe Tournament, type: :model do
|
|||
it { should belong_to :user }
|
||||
it { should have_many :teams }
|
||||
it { should have_many :stages }
|
||||
it { should have_many :beamers }
|
||||
end
|
||||
|
||||
describe 'default beamer' do
|
||||
it 'creates a default beamer automatically' do
|
||||
tournament = create(:tournament)
|
||||
|
||||
expect(tournament.beamers.pluck(:name)).to include(Beamer::DEFAULT_NAME)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#matches' do
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe BeamersController, type: :routing do
|
||||
describe 'routing' do
|
||||
it 'routes to #index' do
|
||||
expect(get: '/tournaments/1/beamers').to route_to(
|
||||
'beamers#index', tournament_id: '1'
|
||||
)
|
||||
end
|
||||
|
||||
it 'routes to #show' do
|
||||
expect(get: '/tournaments/1/beamers/2').to route_to(
|
||||
'beamers#show', tournament_id: '1', id: '2'
|
||||
)
|
||||
end
|
||||
|
||||
it 'routes to #by_name' do
|
||||
expect(get: '/tournaments/1/beamers/by_name').to route_to(
|
||||
'beamers#by_name', tournament_id: '1'
|
||||
)
|
||||
end
|
||||
|
||||
it 'routes to #create' do
|
||||
expect(post: '/tournaments/1/beamers').to route_to(
|
||||
'beamers#create', tournament_id: '1'
|
||||
)
|
||||
end
|
||||
|
||||
it 'routes to #update via PATCH' do
|
||||
expect(patch: '/tournaments/1/beamers/2').to route_to(
|
||||
'beamers#update', tournament_id: '1', id: '2'
|
||||
)
|
||||
end
|
||||
|
||||
it 'routes to #destroy' do
|
||||
expect(delete: '/tournaments/1/beamers/2').to route_to(
|
||||
'beamers#destroy', tournament_id: '1', id: '2'
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Reference in New Issue