From fecdb7db2d31cd78c1f31c72fa49d09b1cd19b23 Mon Sep 17 00:00:00 2001 From: Thor77 Date: Sun, 25 Nov 2018 19:59:45 +0100 Subject: [PATCH 01/12] Add deserialize_params helper --- app/controllers/application_controller.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 803d135..680289d 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -24,4 +24,8 @@ class ApplicationController < ActionController::API ] }, status: :forbidden end + + def deserialize_params(opts) + ActiveModelSerializers::Deserialization.jsonapi_parse(params, opts) + end end From d1f66b18d4d800525d7a3686eeddd3beaee995cf Mon Sep 17 00:00:00 2001 From: Thor77 Date: Sun, 25 Nov 2018 20:03:07 +0100 Subject: [PATCH 02/12] Add tournament serializers SimpleTournamentSerializer excludes relationships and should be used for listings --- app/serializers/simple_tournament_serializer.rb | 5 +++++ app/serializers/tournament_serializer.rb | 6 ++++++ 2 files changed, 11 insertions(+) create mode 100644 app/serializers/simple_tournament_serializer.rb create mode 100644 app/serializers/tournament_serializer.rb diff --git a/app/serializers/simple_tournament_serializer.rb b/app/serializers/simple_tournament_serializer.rb new file mode 100644 index 0000000..c357f36 --- /dev/null +++ b/app/serializers/simple_tournament_serializer.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class SimpleTournamentSerializer < ApplicationSerializer + attributes :name, :code, :description, :public +end diff --git a/app/serializers/tournament_serializer.rb b/app/serializers/tournament_serializer.rb new file mode 100644 index 0000000..4c7c2c6 --- /dev/null +++ b/app/serializers/tournament_serializer.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +class TournamentSerializer < SimpleTournamentSerializer + has_many :teams + has_many :stages +end From 83c76c456cd50effa882dae428106abcb96eda42 Mon Sep 17 00:00:00 2001 From: Thor77 Date: Sun, 25 Nov 2018 20:03:47 +0100 Subject: [PATCH 03/12] Add TournamentsControllers specs --- .../tournaments_controller_spec.rb | 205 ++++++++++++++++++ spec/routing/tournaments_routing_spec.rb | 31 +++ 2 files changed, 236 insertions(+) create mode 100644 spec/controllers/tournaments_controller_spec.rb create mode 100644 spec/routing/tournaments_routing_spec.rb diff --git a/spec/controllers/tournaments_controller_spec.rb b/spec/controllers/tournaments_controller_spec.rb new file mode 100644 index 0000000..d6bec62 --- /dev/null +++ b/spec/controllers/tournaments_controller_spec.rb @@ -0,0 +1,205 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe TournamentsController, type: :controller do + def tournament_ids(response) + deserialize_list(response).map { |t| t[:id].to_i } + end + before do + @tournament = create(:tournament) + @user = @tournament.owner + @another_user = create(:user) + @private_tournament = create(:tournament, user: @another_user, public: false) + @teams = create_list(:detached_team, 4) + end + + describe 'GET #index' do + it 'returns a success response' do + get :index + expect(response).to be_successful + end + + it 'returns all public tournaments' do + get :index + tournaments = deserialize_list response + public_tournaments = tournaments.select { |t| t[:public] } + expect(public_tournaments.size).to eq((Tournament.where public: true).size) + end + + it 'returns no private tournaments for unauthenticated users' do + get :index + tournaments = deserialize_list response + private_tournaments = tournaments.reject { |t| t[:public] } + expect(private_tournaments.size).to eq(0) + end + + it 'returns private tournaments owned by the authenticated user' do + apply_authentication_headers_for @another_user + get :index + expect(tournament_ids(response)).to include(@private_tournament.id) + end + + it 'returns no private tournaments owned by another user' do + apply_authentication_headers_for @user + get :index + expect(tournament_ids(response)).not_to include(@private_tournament.id) + end + end + + describe 'GET #show' do + it 'returns a success response' do + get :show, params: { id: @tournament.to_param } + expect(response).to be_successful + end + + it 'returns the requested tournament' do + get :show, params: { id: @tournament.to_param } + expect(deserialize_response(response)[:id].to_i).to eq(@tournament.id) + end + end + + describe 'POST #create' do + let(:create_data) do + { + data: { + type: 'tournaments', + attributes: { + name: Faker::Dog.name, + description: Faker::Lorem.sentence, + public: false + }, + relationships: { + teams: { + data: @teams.map { |team| { type: 'teams', id: team.id } } + } + } + } + } + end + + before(:each) do + apply_authentication_headers_for @user + end + + context 'with valid params' do + it 'creates a new Tournament' do + expect do + post :create, params: create_data + end.to change(Tournament, :count).by(1) + end + + it 'associates the new tournament with the authenticated user' do + expect do + post :create, params: create_data + end.to change(@user.tournaments, :size).by(1) + end + + it 'associates the given teams with the created tournament' do + new_teams = create_list(:detached_team, 4) + new_teams_create_data = create_data + new_teams_create_data[:data][:relationships][:teams][:data] = \ + new_teams.map { |team| { type: 'teams', id: team.id } } + post :create, params: new_teams_create_data + expect(Tournament.last.teams).to match_array(new_teams) + end + + it 'renders a JSON response with the new tournament' do + post :create, params: create_data + expect(response).to have_http_status(:created) + expect(response.content_type).to eq('application/json') + expect(response.location).to eq(tournament_url(Tournament.last)) + end + end + end + + describe 'PUT #update' do + let(:valid_update) do + { + data: { + type: 'tournaments', + id: @tournament.id, + attributes: { + name: Faker::Dog.name + } + } + } + end + + context 'with valid params' do + context 'without authentication headers' do + it 'renders a unauthorized error response' do + put :update, params: { id: @tournament.to_param }.merge(valid_update) + expect(response).to have_http_status(:unauthorized) + end + end + + context 'as owner' do + before(:each) do + apply_authentication_headers_for @tournament.owner + end + + it 'updates the requested tournament' do + put :update, params: { id: @tournament.to_param }.merge(valid_update) + @tournament.reload + expect(@tournament.name).to eq(valid_update[:data][:attributes][:name]) + end + + it 'renders a JSON response with the tournament' do + put :update, params: { id: @tournament.to_param }.merge(valid_update) + expect(response).to have_http_status(:ok) + expect(response.content_type).to eq('application/json') + end + end + + context 'as another user' do + before do + apply_authentication_headers_for create(:user) + end + + it 'renders a forbidden error response' do + put :update, params: { id: @tournament.to_param }.merge(valid_update) + expect(response).to have_http_status(:forbidden) + end + end + end + end + + describe 'DELETE #destroy' do + context 'without authentication headers' do + it 'renders a unauthorized error response' do + delete :destroy, params: { id: @tournament.to_param } + expect(response).to have_http_status(:unauthorized) + end + end + + context 'as owner' do + before(:each) do + apply_authentication_headers_for @tournament.owner + end + + it 'destroys the requested tournament' do + expect do + delete :destroy, params: { id: @tournament.to_param } + end.to change(Tournament, :count).by(-1) + end + + it 'destroys associated teams' do + expect do + delete :destroy, params: { id: @tournament.to_param } + end.to change(Team, :count).by(-@tournament.teams.size) + end + end + + context 'as another user' do + before do + apply_authentication_headers_for create(:user) + end + + it 'renders a forbidden error response' do + delete :destroy, params: { id: @tournament.to_param } + expect(response).to have_http_status(:forbidden) + end + end + end +end diff --git a/spec/routing/tournaments_routing_spec.rb b/spec/routing/tournaments_routing_spec.rb new file mode 100644 index 0000000..f8d1acf --- /dev/null +++ b/spec/routing/tournaments_routing_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe TournamentsController, type: :routing do + describe 'routing' do + it 'routes to #index' do + expect(get: '/tournaments').to route_to('tournaments#index') + end + + it 'routes to #show' do + expect(get: '/tournaments/1').to route_to('tournaments#show', id: '1') + end + + it 'routes to #create' do + expect(post: '/tournaments').to route_to('tournaments#create') + end + + it 'routes to #update via PUT' do + expect(put: '/tournaments/1').to route_to('tournaments#update', id: '1') + end + + it 'routes to #update via PATCH' do + expect(patch: '/tournaments/1').to route_to('tournaments#update', id: '1') + end + + it 'routes to #destroy' do + expect(delete: '/tournaments/1').to route_to('tournaments#destroy', id: '1') + end + end +end From e81ef81150ae34b06600c82d29cd8ed72b27044a Mon Sep 17 00:00:00 2001 From: Thor77 Date: Sun, 25 Nov 2018 20:04:05 +0100 Subject: [PATCH 04/12] Add TournamentsController --- app/controllers/tournaments_controller.rb | 53 +++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 app/controllers/tournaments_controller.rb diff --git a/app/controllers/tournaments_controller.rb b/app/controllers/tournaments_controller.rb new file mode 100644 index 0000000..a8710e1 --- /dev/null +++ b/app/controllers/tournaments_controller.rb @@ -0,0 +1,53 @@ +# 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] + + # GET /tournaments + def index + tournaments = Tournament.where(public: true).or(Tournament.where(owner: current_user)).order(:created_at) + render json: tournaments, each_serializer: SimpleTournamentSerializer + end + + # GET /tournaments/1 + def show + render json: @tournament + end + + # POST /tournaments + def create + tournament = current_user.tournaments.new tournament_params + + if tournament.save + render json: tournament, status: :created, location: tournament + else + render json: tournament.errors, status: :unprocessable_entity + end + end + + # PATCH/PUT /tournaments/1 + def update + if @tournament.update(tournament_params) + render json: @tournament + else + render json: @tournament.errors, status: :unprocessable_entity + end + end + + # DELETE /tournaments/1 + def destroy + @tournament.destroy + end + + private + + def set_tournament + @tournament = Tournament.find(params[:id]) + end + + def tournament_params + deserialize_params only: %i[name description public teams] + end +end From b66b189bd942a96b983d85f32455e4a13dfea8bd Mon Sep 17 00:00:00 2001 From: Thor77 Date: Sun, 25 Nov 2018 20:04:44 +0100 Subject: [PATCH 05/12] Add routes for TournamentsController --- config/routes.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/config/routes.rb b/config/routes.rb index b52f9d6..12e7419 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -5,4 +5,5 @@ Rails.application.routes.draw do resources :matches, only: %i[show] resources :teams, only: %i[show update] + resources :tournaments end From 9d4d39c135d29c7974945872888f24487411fd1b Mon Sep 17 00:00:00 2001 From: Thor77 Date: Sun, 25 Nov 2018 20:05:10 +0100 Subject: [PATCH 06/12] Make Team.belongs_to :tournament optional necessary for testing TeamController building relationship on create --- app/models/team.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/team.rb b/app/models/team.rb index 8738d12..925ce9f 100644 --- a/app/models/team.rb +++ b/app/models/team.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class Team < ApplicationRecord - belongs_to :tournament + belongs_to :tournament, optional: true has_many :group_scores, dependent: :destroy has_many :scores, dependent: :destroy From 9a41b19e976437dc7ecf5f87654aee9c313e1f52 Mon Sep 17 00:00:00 2001 From: Thor77 Date: Sun, 25 Nov 2018 20:06:06 +0100 Subject: [PATCH 07/12] Add team factory without parent tournament --- spec/factories/teams.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/factories/teams.rb b/spec/factories/teams.rb index 9836e0f..ba32f6d 100644 --- a/spec/factories/teams.rb +++ b/spec/factories/teams.rb @@ -5,4 +5,8 @@ FactoryBot.define do name { Faker::Dog.name } tournament end + + factory :detached_team, class: Team do + name { Faker::Dog.name } + end end From 3bb1e741726e66ad5ec69d94d00fd462ac366628 Mon Sep 17 00:00:00 2001 From: Thor77 Date: Sun, 25 Nov 2018 20:06:58 +0100 Subject: [PATCH 08/12] Modify local instead of global request in apply_authentication_headers_for helper to allow usage of different authenticated user in one testcase --- spec/auth_helpers.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/auth_helpers.rb b/spec/auth_helpers.rb index 27869d4..ea55d1f 100644 --- a/spec/auth_helpers.rb +++ b/spec/auth_helpers.rb @@ -3,6 +3,6 @@ module AuthHelpers def apply_authentication_headers_for(user) user_headers = user.create_new_auth_token - @request.headers.merge!(user_headers) + request.headers.merge!(user_headers) end end From 0a583ebe53f092d2859e223c8e2af41bed0dfdfa Mon Sep 17 00:00:00 2001 From: Thor77 Date: Sun, 25 Nov 2018 20:08:21 +0100 Subject: [PATCH 09/12] Remove unused match_params --- app/controllers/matches_controller.rb | 6 ------ 1 file changed, 6 deletions(-) diff --git a/app/controllers/matches_controller.rb b/app/controllers/matches_controller.rb index a00c1c7..6dea146 100644 --- a/app/controllers/matches_controller.rb +++ b/app/controllers/matches_controller.rb @@ -5,10 +5,4 @@ class MatchesController < ApplicationController def show render json: Match.find(params[:id]), include: ['scores.score', 'scores.team'], status: status end - - private - - def match_params - ActiveModelSerializers::Deserialization.jsonapi_parse(params, only: [:state]) - end end From 4c05a7222eec17caf0eff99df60a4a3cfacf62c1 Mon Sep 17 00:00:00 2001 From: Thor77 Date: Sun, 25 Nov 2018 20:08:38 +0100 Subject: [PATCH 10/12] Use deserialize_params helper --- app/controllers/teams_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/teams_controller.rb b/app/controllers/teams_controller.rb index fde8269..b61d202 100644 --- a/app/controllers/teams_controller.rb +++ b/app/controllers/teams_controller.rb @@ -26,6 +26,6 @@ class TeamsController < ApplicationController end def team_params - ActiveModelSerializers::Deserialization.jsonapi_parse(params, only: [:name]) + deserialize_params only: %i[name] end end From 29b570bafa3a587163ab63011d888c38b845cdbd Mon Sep 17 00:00:00 2001 From: Thor77 Date: Sun, 25 Nov 2018 20:08:55 +0100 Subject: [PATCH 11/12] Add deserialize_list helper --- spec/deserialize_helpers.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/spec/deserialize_helpers.rb b/spec/deserialize_helpers.rb index 2be5bf4..a6837b4 100644 --- a/spec/deserialize_helpers.rb +++ b/spec/deserialize_helpers.rb @@ -4,4 +4,10 @@ module DeserializeHelpers def deserialize_response(response) ActiveModelSerializers::Deserialization.jsonapi_parse(JSON.parse(response.body)) end + + def deserialize_list(response) + JSON.parse(response.body, symbolize_names: true)[:data].map do |raw_obj| + raw_obj[:attributes].merge raw_obj.except(:attributes) + end + end end From 70cc88920b28eed4491e0e8eb5951e5c05b4f39f Mon Sep 17 00:00:00 2001 From: Thor77 Date: Sun, 25 Nov 2018 20:09:05 +0100 Subject: [PATCH 12/12] Remove unused global variables --- spec/controllers/matches_controller_spec.rb | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/spec/controllers/matches_controller_spec.rb b/spec/controllers/matches_controller_spec.rb index 47fc9fb..5b83fa8 100644 --- a/spec/controllers/matches_controller_spec.rb +++ b/spec/controllers/matches_controller_spec.rb @@ -3,16 +3,6 @@ require 'rails_helper' RSpec.describe MatchesController, type: :controller do - let(:valid_attributes) do - skip('Add a hash of attributes valid for your model') - end - - let(:invalid_attributes) do - skip('Add a hash of attributes invalid for your model') - end - - let(:valid_session) { {} } - before do @match = create(:match) @match.scores = create_pair(:score)