diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 450068c..803d135 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -10,4 +10,18 @@ class ApplicationController < ActionController::API def configure_permitted_parameters devise_parameter_sanitizer.permit(:sign_up, keys: [:username]) end + + private + + def require_owner!(owner) + render_forbidden_error if owner != current_user + end + + def render_forbidden_error + render json: { + errors: [ + 'Only the parent tournament owner can update this resource' + ] + }, status: :forbidden + end end diff --git a/app/controllers/teams_controller.rb b/app/controllers/teams_controller.rb new file mode 100644 index 0000000..fde8269 --- /dev/null +++ b/app/controllers/teams_controller.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +class TeamsController < ApplicationController + before_action :set_team, only: %i[show update] + before_action :authenticate_user!, only: %i[update] + before_action -> { require_owner! @team.owner }, only: %i[update] + + # GET /teams/1 + def show + render json: @team + end + + # PATCH/PUT /teams/1 + def update + if @team.update(team_params) + render json: @team + else + render json: @team.errors, status: :unprocessable_entity + end + end + + private + + def set_team + @team = Team.find(params[:id]) + end + + def team_params + ActiveModelSerializers::Deserialization.jsonapi_parse(params, only: [:name]) + end +end diff --git a/app/models/team.rb b/app/models/team.rb index 023b9db..8738d12 100644 --- a/app/models/team.rb +++ b/app/models/team.rb @@ -6,4 +6,6 @@ class Team < ApplicationRecord has_many :scores, dependent: :destroy validates :name, presence: true + + delegate :owner, to: :tournament end diff --git a/config/routes.rb b/config/routes.rb index b10df31..b52f9d6 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -4,4 +4,5 @@ Rails.application.routes.draw do mount_devise_token_auth_for 'User', at: 'users' resources :matches, only: %i[show] + resources :teams, only: %i[show update] end diff --git a/spec/auth_helpers.rb b/spec/auth_helpers.rb new file mode 100644 index 0000000..27869d4 --- /dev/null +++ b/spec/auth_helpers.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module AuthHelpers + def apply_authentication_headers_for(user) + user_headers = user.create_new_auth_token + @request.headers.merge!(user_headers) + end +end diff --git a/spec/controllers/teams_controller_spec.rb b/spec/controllers/teams_controller_spec.rb new file mode 100644 index 0000000..2537aa7 --- /dev/null +++ b/spec/controllers/teams_controller_spec.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe TeamsController, type: :controller do + before do + @team = create(:team) + @owner = @team.owner + end + + describe 'GET #show' do + it 'returns a success response' do + get :show, params: { id: @team.to_param } + expect(response).to be_successful + end + + it 'should return the correct team' do + get :show, params: { id: @team.to_param } + body = deserialize_response response + expect(body[:name]).to eq(@team.name) + end + end + + describe 'PUT #update' do + let(:valid_update) do + { + data: { + id: @team.id, + type: 'teams', + attributes: { + name: Faker::Dog.name + } + } + } + end + + context 'with valid params as owner' do + before(:each) do + apply_authentication_headers_for @owner + end + + it 'updates the requested team' do + put :update, params: { id: @team.to_param }.merge(valid_update) + @team.reload + expect(response).to be_successful + expect(@team.name).to eq(valid_update[:data][:attributes][:name]) + end + + it 'renders a response with the updated team' do + put :update, params: { id: @team.to_param }.merge(valid_update) + expect(response).to be_successful + body = deserialize_response response + expect(body[:name]).to eq(valid_update[:data][:attributes][:name]) + end + end + + context 'with valid params as another user' do + before(:each) do + apply_authentication_headers_for create(:user) + end + + it 'renders a forbidden error response' do + put :update, params: { id: @team.to_param }.merge(valid_update) + expect(response).to have_http_status(:forbidden) + end + end + end +end diff --git a/spec/deserialize_helpers.rb b/spec/deserialize_helpers.rb new file mode 100644 index 0000000..2be5bf4 --- /dev/null +++ b/spec/deserialize_helpers.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module DeserializeHelpers + def deserialize_response(response) + ActiveModelSerializers::Deserialization.jsonapi_parse(JSON.parse(response.body)) + end +end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index dce28cc..3d93ab1 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -1,7 +1,9 @@ # frozen_string_literal: true -# This file is copied to spec/ when you run 'rails generate rspec:install' require 'spec_helper' +require 'auth_helpers' +require 'deserialize_helpers' + ENV['RAILS_ENV'] ||= 'test' require File.expand_path('../config/environment', __dir__) # Prevent database truncation if the environment is production @@ -61,6 +63,9 @@ RSpec.configure do |config| # arbitrary gems may also be filtered via: # config.filter_gems_from_backtrace("gem name") config.include FactoryBot::Syntax::Methods + config.include Devise::Test::ControllerHelpers, type: :controller + config.include AuthHelpers + config.include DeserializeHelpers end Shoulda::Matchers.configure do |config| diff --git a/spec/routing/teams_routing_spec.rb b/spec/routing/teams_routing_spec.rb new file mode 100644 index 0000000..b5f5a40 --- /dev/null +++ b/spec/routing/teams_routing_spec.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe TeamsController, type: :routing do + describe 'routing' do + it 'routes to #show' do + expect(get: '/teams/1').to route_to('teams#show', id: '1') + end + + it 'routes to #update via PUT' do + expect(put: '/teams/1').to route_to('teams#update', id: '1') + end + + it 'routes to #update via PATCH' do + expect(patch: '/teams/1').to route_to('teams#update', id: '1') + end + end +end