All Files (98.46% covered at 270.76 hits/line)
73 files in total.
3110 relevant lines.
3062 lines covered and
48 lines missed
# Base class for this application's controllers
#
- 1
class ApplicationController < ActionController::API
- 1
respond_to :json
- 1
rescue_from ::ActiveRecord::RecordNotFound, with: :rescue_record_not_found
# Include JSON serialization
- 1
include ::ActionController::Serialization
# Include authentication support
- 1
include Authenticable
- 1
protected
# Render an HTTP error on ActiveRecord::RecordNotFound
- 1
def rescue_record_not_found
- 11
render json: { errors: 'Record not found' }, status: :not_found
end
end
# Add authentication support to a controller
- 1
module Authenticable
# Get user associated with auth token.
# Overrides devise implementation.
#
# * *Returns* :
# - User
#
- 1
def current_user
- 184
@current_user ||= User.find_by(auth_token: header_token)
end
# Check for a current user. If not, render
# an error.
# Overrides devise implementation.
#
- 1
def authorize_user!
- 144
if header_token.blank?
# Anonymous users can't request current user
- 25
render json: { errors: 'Login required' }, status: :forbidden
else
# Token may be invalid
- 119
render json: { errors: 'Not authorized' }, status: :unauthorized unless user_signed_in?
end
end
# Check for a current user.
# Overrides device implementation.
#
- 1
def user_signed_in?
- 120
current_user.present?
end
- 1
private
- 1
def header_token
- 284
request.headers['Authorization']
end
end
# Match loading for controllers
- 1
module MatchLoader
# Load match, sets, games, teams and players at once.
# * *Args* :
# - +match_id+ -> Id of Match
# * *Returns* : Relation
- 1
def self.eager_load_match(match_id)
Match
.includes(first_team: [:first_player,
:second_player],
second_team: [:first_player,
:second_player],
match_sets: [set_games:
[:player_server,
:team_winner]])
- 58
.find(match_id)
end
end
# Controller for match scoreboards
#
# * Renders a match with sets and games using V1::MatchScoreboardSerializer
# * Executes commands to play the match
#
- 1
class V1::MatchScoreboardController < ApplicationController
- 1
rescue_from ::Exceptions::UnknownOperation, with: :when_unknown_operation
- 1
before_action :authorize_user!, only: [:update]
- 1
before_action :set_match_eager_load, only: [:show, :update]
# Render the match score
# * *Params*
# * +:id+ - id of a Match
# * *Response*
# * Serialized Match score
- 1
def show
- 6
render json: V1::MatchScoreboardSerializer.new(@match, root: false)
end
# Execute an action by calling Match.play_match!
# * *Params*
# * +:action+ - action such as +:win_game+
# * +:version+ - match play version number
# * +:team+ - id of a Team to win a game
# * +:player+ - id of a Player to serve or win a game
# * *Response*
# * Serialized Match score
- 1
def update
- 27
request_params = match_scoreboard_params
- 27
action = request_params[:action].to_sym
- 27
version = request_params[:version]
- 27
team_id = request_params[:team]
- 27
player_id = request_params[:player] unless team_id
- 27
play_params = {}
- 27
play_params[:version] = version if version
- 27
play_params[:opponent] =
if team_id
- 4
Team.find(team_id) if team_id
elsif player_id
- 14
Player.find(player_id) if player_id
end
- 27
play_match {
- 27
@match.play_match!(action, play_params)
}
end
- 1
private
- 1
def set_match_eager_load
# @match = self.class.eager_load_match params[:id]
- 35
@match = MatchLoader::eager_load_match params[:id]
end
- 1
def when_unknown_operation(exception)
- 4
render json: { errors: exception.message }, status: :unprocessable_entity
end
- 1
def match_scoreboard_params
- 27
params.require(:match_scoreboard).permit(:action,
:team,
:player,
:version)
end
- 1
def play_match
- 27
begin
- 27
yield
# TODO: Notify clients of a change
- 20
reload_match
rescue ::Exceptions::UnknownOperation
# raise HTTP error
- 4
raise
rescue => e
# render the scoreboard with an error element
- 3
reload_match ({ other: e.message })
end
- 23
render json: V1::MatchScoreboardSerializer.new(@match, root: false)
end
- 1
def reload_match(errors=nil)
- 23
@match = MatchLoader::eager_load_match params[:id]
- 23
@match.errors.messages.merge! errors if errors
end
end
# Controller for matches
#
# * Renders a list of all matches
# * Renders one particular match
# * Creates a new match
# * Updates a match
# * Deletes a match
# * Serializes a Match using V1::MatchSerializer
#
- 1
class V1::MatchesController < ApplicationController
- 1
before_action :authorize_user!, only: [:create, :update, :destroy]
- 1
before_action :set_match, only: [:show, :update, :destroy]
- 1
@match = nil
# Get a list of all matches,
# sorted by match title
# * *Response*
# * Serialized array of matches
- 1
def index
- 2
@matches = Match.order 'lower(title)'
- 2
render json: @matches, serializer: V1::ApplicationArraySerializer
end
# Get a particular match
# * *Params*
# * +:id+ - id of a Match
# * *Response*
# * Match
- 1
def show
- 4
render json: @match
end
# Create a match
# * *Request*
# * +:title+ - match title
# * +:scoring+ - scoring kind
# * +:doubles+ - true for doubles
# * +:first_team_id+ - id of a Team
# * +:second_team_id+ - id of a Team
# * +:first_player_id+ - id of a Player
# * +:second_player_id+ - id of a Player
# Teams are for a doubles match. Players are for a singles match.
# * *Response*
# * serialized Match or HTTP error
- 1
def create
- 16
@match = create_match
- 16
if @match.save
- 8
render json: @match, status: :created, location: @match
else
- 8
render json: { errors: @match.errors }, status: :unprocessable_entity
end
end
# Update a match
# * *Params*
# * +:id+ - id of a Match
# * *Request*
# * +:title+ - change title
# * +:scoring+ - change scoring kind
# * +:doubles+ - change doubles
# * +:first_team_id+ - change first team
# * +:second_team_id+ - change second team
# * +:first_player_id+ - change first player
# * +:second_player_id+ - change second player
# Teams are for a doubles match. Players are for a singles match.
# * *Response*
# * serialized Match or HTTP error
- 1
def update
- 18
doubles = match_params_doubles?(@match.doubles)
- 18
update_sym = if doubles
- 11
:update_doubles_match
else
- 7
:update_singles_match
end
- 18
if send(update_sym, @match, match_params(doubles))
- 16
render json: @match, status: :ok
else
- 2
render json: { errors: @match.errors }, status: :unprocessable_entity
end
end
# Delete a match
# * *Params*
# * +:id+ - id of a Match
# * *Response*
# * +:no_content+ or HTTP error
- 1
def destroy
- 1
@match.destroy
- 1
head :no_content
end
- 1
private
# Opponents for a doubles match and for a singles match are different.
# Singles match has players. Doubles match has teams.
- 1
def match_params(doubles)
- 34
params_var = params_require
- 34
permit = [:title, :scoring, :doubles]
- 34
permit += if doubles
- 24
[:first_team_id, :second_team_id]
else
- 10
[:first_player_id, :second_player_id]
end
- 34
params_var.permit(permit)
end
- 1
def match_params_doubles?(default_value=false)
- 34
exists_doubles_param? ? doubles_param? : default_value
end
- 1
def params_require
- 94
params.require :match
end
- 1
def doubles_param?
- 26
params_require[:doubles].to_s == 'true'
end
- 1
def exists_doubles_param?
- 34
!params_require[:doubles].nil?
end
- 1
def set_match
- 26
@match = Match.find(params[:id])
end
- 1
def create_doubles_match(params_var)
- 13
Match.new params_var
end
- 1
def update_doubles_match(match, params_var)
- 11
match.update params_var
end
- 1
def create_match
- 16
doubles = match_params_doubles?
- 16
params_var = match_params(doubles)
- 16
if doubles
- 13
create_doubles_match params_var
else
- 3
create_singles_match params_var
end
end
- 1
def create_singles_match(params_var)
- 3
Match.new player_ids_to_team_ids(params_var)
end
- 1
def update_singles_match(match, params_var)
- 7
match.update player_ids_to_team_ids(params_var)
end
# Change hash to contain team ids rather than player ids.
- 1
def player_ids_to_team_ids(params_var)
- 10
new_params = player_id_to_team_id(params_var, 'first')
- 10
player_id_to_team_id(new_params, 'second')
end
# Requests to create a singles match includes first_player_id and
# second_player_id.
# However, match model expects first_team_id and second_team_id.
# So, replace first_player_id with first_team_id, for example.
# If necessary, Player.#singles_team! will create a team to
# represent a singles player.
- 1
def player_id_to_team_id(params_var, prefix)
- 20
player_id_sym = "#{prefix}_player_id".to_sym
- 20
player_id = params_var[player_id_sym]
- 20
unless player_id.blank?
- 14
params_var["#{prefix}_team_id".to_sym] =
Player.find(player_id).singles_team!.id
- 14
params_var.except! player_id_sym
end
- 20
params_var
end
end
# Controller for players
#
# * Renders a list of all players
# * Renders one player
# * Creates a new player
# * Updates a player
# * Deletes a player
# * Serializes a Player using V1::PlayerSerializer
#
- 1
class V1::PlayersController < ApplicationController
- 1
before_action :authorize_user!, only: [:update, :create, :destroy]
- 1
before_action :set_player, only: [:show, :update, :destroy]
# Get a list of all players,
# sorted by player name
# * *Response*
# * serialized array of players
- 1
def index
- 2
@players = Player.order 'lower(name)'
- 2
render json: @players, serializer: V1::ApplicationArraySerializer
end
# Get a particular player
# * *Params*
# * +:id+ - player id
# * *Response*
# * serialized Player
- 1
def show
- 3
render json: @player
end
# Create a player
# * *Request*
# * +:name+ - player name
# * *Response*
# * serialized Player or HTTP error
- 1
def create
- 7
@player = Player.new(player_params)
- 7
if @player.save
- 3
render json: @player, status: :created, location: @player
else
- 4
render json: {errors: @player.errors}, status: :unprocessable_entity
end
end
# Update a player
# * *Params*
# * +:id+ - player id
# * *Request*
# * +:name+ - different player name
# * *Response*
# * serialized Player or HTTP error
- 1
def update
- 7
if @player.update(player_params)
- 3
render json: @player, status: :ok
else
- 4
render json: {errors: @player.errors}, status: :unprocessable_entity
end
end
# Delete a player
# A player in a match or on a team may not be
# deleted.
# * *Params*
# * +:id+ - player id
# * *Response*
# * +:no_content+ or HTTP error
- 1
def destroy
- 5
if @player.destroy
- 1
head :no_content
else
- 4
render json: {errors: @player.errors}, status: :unprocessable_entity
end
end
- 1
private
- 1
def set_player
- 18
@player = Player.find(params[:id])
end
- 1
def player_params
- 14
params[:player].permit :name
end
end
# Controller for a user session
#
# * Validates credentials and returns user attributes
# * Regenerates the authentication token of a user
#
- 1
class V1::SessionsController < ApplicationController
# Login a user
# Validate user credentials. If valid, respond with
# an authentication token for the user.
# * *Request*
# * +:username+
# * +:password+
# * *Response*
# * User or HTTP error. See V1::SessionSerializer
- 1
def create
- 3
session = params[:session]
- 3
user_password = session[:password] if session
- 3
user_username = session[:username] if session
- 3
user = user_username.present? && User.find_by(username: user_username)
- 3
if user && user.valid_password?(user_password)
# The following two lines prevent a user from being logged in
# on multiple devices.
# user.generate_authentication_token!
# user.save!
- 1
render json: V1::SessionSerializer.new(user, root: false)
else
- 2
render json: { errors: 'Invalid username or password' }, status: 422
end
end
# Clear the authentication token associated with a user
# * *Params*
# * +:id+ - authentication token
# * *Response*
# * +:no_content+ or HTTP error
- 1
def destroy
- 2
user = User.find_by(auth_token: params[:id])
- 2
if user
- 1
user.generate_authentication_token!
- 1
user.save!
end
- 2
head :no_content
end
end
# Controller for teams
#
# * Renders a list of all teams
# * Renders a particular team
# * Creates a new team
# * Updates a team
# * Deletes a team
# Teams can not be deleted if they are in a match
#
- 1
class V1::TeamsController < ApplicationController
- 1
before_action :authorize_user!, only: [:update, :create, :destroy]
- 1
before_action :set_team, only: [:show, :update, :destroy]
# Get a list of all doubles teams,
# sorted by team name.
# Singles teams are for internal use so are
# not shown to end users.
# * *Response*
# * List of teams
- 1
def index
- 2
@teams = Team.where(doubles: true).order 'lower(name)'
- 2
render json: @teams, serializer: V1::ApplicationArraySerializer
end
# Get a particular team
# * *Params*
# * +:id+ - team id
# * *Response*
# * Team
- 1
def show
- 6
render json: @team
end
# Create a team
# * *Request*
# * +:name+ - team name
# * +:first_player_id+ - first player on team
# * +:second_player_id+ - second player on team
# * *Response*
# * Team
- 1
def create
- 16
json = team_params
- 16
json[:doubles] = true
- 16
@team = Team.new(json)
- 16
if @team.save
- 6
render json: @team, status: :created, location: @team
else
- 10
render json: {errors: @team.errors}, status: :unprocessable_entity
end
end
# Update a team
# * *Params*
# * +:id+ - team id
# * *Request*
# * +:name+ - different team name
# * +:first_player_id+ - different first player
# * +:second_player_id+ - different second player
# * *Response*
# * Team or HTTP error
- 1
def update
- 4
if @team.update(team_params)
- 2
render json: @team, status: :ok
else
- 2
render json: {errors: @team.errors}, status: :unprocessable_entity
end
end
# Delete a team
# A team in a match may not be
# deleted
# * *Params*
# * +:id+ - team id
# * *Response*
# * +:no_content+ or HTTP error
- 1
def destroy
- 3
if @team.destroy
- 1
head :no_content
else
- 2
render json: {errors: @team.errors}, status: :unprocessable_entity
end
end
- 1
private
- 1
def set_team
- 16
@team = Team.find(params[:id])
end
- 1
def team_params
- 20
params_var = params[:team].permit(:first_player_id,
:second_player_id,
:name)
# team name may be nil
- 20
params_var[:name] = nil if params_var[:name].blank?
- 20
params_var
end
end
# Controller for the current user
#
# Renders the current user using V1::UserSerializer.
# The current user is determined
# by the authentication token in the request header
#
- 1
class V1::UserController < ApplicationController
- 1
before_action :authorize_user!, only: [:show]
# Get the current user
# * *Response*
# * serialized User
- 1
def show
- 2
render json: current_user
end
end
# Custom exception classes
#
- 1
module Exceptions
# Base class for application exceptions
- 1
class ApplicationError < StandardError; end
# Could not find an object that was needed to perform an operation
- 1
class NotFound < ApplicationError; end
# Attempt to execute an operation that is not allowed at this time
- 1
class InvalidOperation < ApplicationError; end
# Attempt to execute an operation that is not recognized
- 1
class UnknownOperation < ApplicationError; end
end
# Executes actions to play a match. Executes the ':win_game' action to
# win the current game in progress, for example.
- 1
module MatchPlay
# Class to help work with actions:
# * Indicate whether a particular action is enabled
# * Get a hash of all enabled actions
# * Execute an action
# actions include +:win_game+, +:start_game+, and +:discard_play+.
# See Match.play_match!
#
- 1
class PlayActions
# * *Args*
# - +match+ -> Match
- 1
def initialize(match)
- 239
@match = match
end
# Lookup the methods to handle a particular action
#
# * *Args* :
# - +action+ -> +:win_game+, +:start_game+ etc.
# * *Returns* : Hash or nil
# - +:exec+ - method to execute the action
# - +:query+ - method to determine if the action is enabled
- 1
def lookup_method(action)
- 6056
action = action.to_sym
- 6056
play_methods_table[action] if play_methods_table.has_key? action
end
# Generate a hash of valid actions
#
# * *Returns* : Hash
# === Example
# {
# start_game: true,
# discard_play: true,
# remove_last_change: true
# }
- 1
def valid_actions
- 54
result = {}
- 54
play_methods_table.each do |k, v|
- 540
if v[:query].call
- 122
result[k] = true
end
end
- 54
result
end
- 1
private
- 1
attr_reader :match
- 1
def play_methods_table
- 12163
unless @methods
- 239
@methods = {}
- 239
syms = [:start_play,
:discard_play, :start_set,
:start_game, :start_tiebreak, :remove_last_change,
:start_match_tiebreak,
:win_game, :win_tiebreak, :win_match_tiebreak]
- 239
syms.each do |sym|
- 2390
@methods[sym] =
{
exec: nil,
query: method("#{sym}?")
}
end
- 239
[:win_game, :win_tiebreak, :win_match_tiebreak].each do |sym|
# Methods with team parameter
- 717
@methods[sym][:exec] = lambda do |options|
- 1334
team = team_from_options options if options
- 1334
raise Exceptions::UnknownOperation, "Unknown team for action: #{sym}" unless team
- 1329
method("#{sym}!").call(team)
end
end
# Method with optional player parameter
- 239
[:start_game].each do |sym|
- 239
@methods[sym][:exec] = lambda do |options|
- 1261
player = player_from_options options if options
- 1261
method("#{sym}!").call(player)
end
end
# Methods with no parameter
- 239
syms.each do |sym|
- 2390
if @methods[sym][:exec].nil?
- 1434
@methods[sym][:exec] = lambda do |options|
- 326
method("#{sym}!").call
end
end
end
end
- 12163
@methods
end
- 1
def team_from_options(options)
- 1331
opponent = opponent_from_options options
- 1331
if opponent[:player]
- 6
match.doubles ? nil : opponent[:player].singles_team!
else
- 1325
opponent[:team]
end
end
- 1
def player_from_options(options)
- 1239
opponent = opponent_from_options options
- 1239
if opponent[:team]
- 2
opponent[:team].first_player
else
- 1237
opponent[:player]
end
end
- 1
def opponent_from_options(options)
- 2570
result = {
player: nil,
team: nil
}
- 2570
opponent = options[:opponent]
- 2570
if opponent
- 1500
if opponent.is_a? Team
- 1325
result[:team] = opponent
elsif opponent.is_a? Player
- 175
result[:player] = opponent
end
end
- 2570
result
end
# Operations to play a match.
#
# The following methods start a match, start a set, start a game,
# win a game, etc. The methods operate on the match at the current point.
# For example, win_game! applies to the current game in progress. If a method
# is called out of sequence, an exception is raised. Use the "?" methods
# to determine if an operation is allowed.
#
# Starts the match. Creates the first set.
- 1
def start_play?
- 306
!match.started
end
- 1
def start_play!
- 171
unless start_play?
- 1
raise Exceptions::InvalidOperation, 'Can\'t start play'
end
- 170
match.started = true
- 170
match.save!
- 170
set = create_new_set
- 170
set.save!
end
# Starts a new set. NOP if called immediately after start_play
- 1
def start_set?
- 193
start.set?
end
- 1
def start_set!
- 65
unless start.set?
- 1
raise Exceptions::InvalidOperation,
"Can\'t start set #{match.match_sets.count}"
end
- 64
set = create_new_set
- 64
set.save!
- 64
game = start.game
- 64
game.save!
end
# Start a new game. A game will be added to the current set.
# The server will be computed and assigned to the game.
- 1
def start_game?
- 2569
start.game?
end
- 1
def start_game!(player_server = nil)
- 1261
if player_server
- 171
update_player_server!(player_server)
end
- 1259
unless start.game?
- 1
last_set_var = match.last_set
- 1
raise Exceptions::InvalidOperation,
"can\'t start game #{last_set_var ? last_set_var.set_games.count : 0}"
end
- 1258
game = start.game
- 1255
game.save!
- 1255
game
end
- 1
def win_game?
- 160
win.game?
end
- 1
def win_game!(team)
- 1272
unless win.game?
- 1
raise Exceptions::InvalidOperation,
"Can\'t win game #{match.last_set ? match.last_set.set_games.count : 0}"
end
- 1271
game = win.game team
- 1271
game.save!
- 1271
complete!
- 1271
game
end
# Start a tiebreak at the end of a set.
- 1
def start_tiebreak?
- 112
start.tiebreak?
end
- 1
def start_tiebreak!
- 36
unless start.tiebreak?
- 2
raise Exceptions::InvalidOperation, 'can\'t start tiebreak'
end
- 34
game = start.tiebreak
- 34
game.save!
- 34
game
end
# Win a tiebreak. The winning team must be provided.
- 1
def win_tiebreak?
- 59
win.tiebreak?
end
- 1
def win_tiebreak!(team)
- 33
unless win.tiebreak?
- 2
raise Exceptions::InvalidOperation, 'can\'t win tiebreak'
end
- 31
game = win.tiebreak team
- 31
game.save!
- 31
complete!
- 31
game
end
# Start a match tiebreak. A match tiebreak can be
# created after each player has won an equal number of sets.
- 1
def start_match_tiebreak?
- 226
start.match_tiebreak?
end
- 1
def start_match_tiebreak!
- 38
unless start.match_tiebreak?
- 1
raise Exceptions::InvalidOperation, 'Can\'t start match tiebreak'
end
- 37
set = create_new_set
- 37
set.save!
- 37
game = start.tiebreak
- 37
game.save!
end
# Win the match tiebreak.
- 1
def win_match_tiebreak?
- 103
win.match_tiebreak?
end
- 1
def win_match_tiebreak!(team)
- 24
unless win.match_tiebreak?
- 2
raise Exceptions::InvalidOperation, 'Can\'t win match tiebreak'
end
- 22
game = win.match_tiebreak team
- 22
game.save!
- 22
complete!
- 22
game
end
# Discard all of the match scoring.
- 1
def discard_play?
- 59
match.started
end
- 1
def discard_play!
- 2
unless discard_play?
- 1
raise Exceptions::InvalidOperation, 'Can\'t discard play'
end
- 1
match.match_sets.destroy_all
- 1
match.first_player_server = nil
- 1
match.second_player_server = nil
- 1
match.started = false
- 1
match.team_winner = nil
- 1
match.save!
end
# Back out one scoring operation.
- 1
def remove_last_change!
- 14
unless remove_last_change?
- 1
raise Exceptions::InvalidOperation, 'Can\'t remove last change'
end
- 13
RemoveLastChange.new(match).remove_last
end
- 1
def remove_last_change?
- 68
match.started
end
# Utility methods and helpers
# Update first or second player serving in the match
- 1
def update_player_server!(player)
- 171
raise Exceptions::InvalidOperation, 'Player required' unless player
- 171
raise Exceptions::InvalidOperation, 'Invalid type' unless player.is_a? Player
- 171
if match.first_player_server.nil?
- 140
match.first_player_server = player
- 140
match.save!
- 31
elsif match.second_player_server.nil?
- 31
match.second_player_server = player
- 31
match.save!
end
end
- 1
def create_new_set
- 271
ordinal = match.match_sets.count + 1
- 271
set = MatchSet.new(match_id: match.id,
ordinal: ordinal,
scoring: match.scoring_of_set(ordinal))
- 271
match.match_sets << set
- 271
set # fluent
end
- 1
def complete!
- 1324
CompleteSetAndMatch.new(match).complete!
end
- 1
def start
- 5891
@start ||= StartNext.new(match)
end
- 1
def win
- 2975
@win ||= WinGame.new(match)
end
end
# Class to remove the last scoring change. Removing the last scoring change may
# involve deleting games or sets, or changing attribute values.
- 1
class RemoveLastChange
# * *Args*
# - +match+ -> Match
- 1
def initialize(match)
- 13
@match = match
end
# Execute +:remove_last_change+ action by updating and/or
# deleting entities.
- 1
def remove_last
- 13
@save_list = []
- 13
@destroy_list = []
- 13
if match.team_winner
- 2
remove_match_complete
elsif match.last_set
- 11
remove_last_set_change(match.last_set)
end
- 13
save_list.each(&:save!)
- 13
destroy_list.each(&:destroy)
end
- 1
private
- 1
attr_reader :match
# List of entities that need to be saved in order
# to remove the last change
- 1
attr_reader :save_list
# List of entities that need to be destroyed in
# order to remove the last change
- 1
attr_reader :destroy_list
- 1
def remove_match_complete
- 2
match.team_winner = nil
- 2
save_list << match
- 2
remove_last_set_complete match.last_set
end
- 1
def remove_last_set_change(last_set_var)
- 11
if last_set_var.team_winner
- 1
remove_last_set_complete(last_set_var)
- 10
elsif last_set_var.set_games.count == 0
# First set may have zero games. Other sets always start with one.
- 1
undo_start_match(last_set_var)
else
- 9
remove_last_game_change(last_set_var.last_game)
end
end
- 1
def remove_last_set_complete(last_set_var)
- 3
last_set_var.team_winner = nil
- 3
save_list << last_set_var
- 3
remove_last_game_change(last_set_var.last_game)
end
- 1
def undo_start_match(last_set_var)
- 1
match.started = false
- 1
save_list << match
- 1
destroy_list << last_set_var
end
- 1
def remove_last_game_change(last_game_var)
- 12
last_set_var = last_game_var.match_set
- 12
if last_game_var.team_winner
# Undo won
- 4
last_game_var.team_winner = nil
- 4
save_list << last_game_var
else
# Undo start game
# Discard set if first game in set but not first game in match
- 8
last = if last_set_var.ordinal > 1 && last_set_var.set_games.count == 1
- 2
last_set_var
else
- 6
last_game_var
end
- 8
destroy_list << last
- 8
save_list << match if undo_first_servers(last_set_var)
end
end
- 1
def undo_first_servers(last_set_var)
- 8
if match.match_sets.count == 1
- 6
games_count = last_set_var.set_games.count
- 6
match.second_player_server = nil if games_count <= 2
- 6
match.first_player_server = nil if games_count <= 1
- 6
true
end
end
end
# Class to help with starting games, tiebreaks, sets, and match tiebreaks
- 1
class StartNext
# * *Args*
# - +match+ -> Match
- 1
def initialize(match)
- 185
@match = match
end
# Indicate if a game can be started
- 1
def game?
- 3828
start_game_kind? :game
end
# Indicate if a tiebreak game can be started
- 1
def tiebreak?
- 148
start_game_kind? :tiebreak
end
# Indicate if a set can be started
- 1
def set?
- 258
start_set_kind? :set
end
# Indicate if the match tiebreak set can be started
- 1
def match_tiebreak?
- 264
start_set_kind? :tiebreak
end
# Start the next game
- 1
def game
- 1322
start_game_kind :game
end
# Start the next tiebreak game
- 1
def tiebreak
- 71
start_game_kind :tiebreak
end
- 1
private
- 1
attr_reader :match
- 1
def start_game_kind?(kind)
- 3976
last_set_var = match.last_set
- 3976
if last_set_var && last_set_var.state == :in_progress &&
no_game_in_progress(last_set_var)
- 3762
(kind == :tiebreak) ==
- 3762
last_set_var.tiebreak_game?(last_set_var.set_games.count + 1)
end
end
- 1
def no_game_in_progress(last_set_var)
- 3925
last_game = last_set_var.set_games.last
- 3925
last_game.nil? || last_game.state == :finished
end
- 1
def start_set_kind?(kind)
- 522
last_set_var = match.last_set
- 522
if match.state == :in_progress && no_set_in_progress?(last_set_var)
- 246
(kind == :tiebreak) ==
- 246
match.tiebreak_set?(match.match_sets.count + 1)
end
end
- 1
def no_set_in_progress?(last_set_var)
- 472
last_set_var.nil? || last_set_var.state == :complete
end
- 1
def start_game_kind(kind)
- 1393
last_set_var = match.last_set
- 1393
raise Exceptions::InvalidOperation, 'No set' unless last_set_var
- 1393
game = SetGame.new(ordinal: match.next_game_ordinal,
tiebreaker: kind == :tiebreak)
- 1393
assign_game_server(game) unless game.tiebreak?
- 1390
last_set_var.set_games << game
- 1390
game
end
- 1
def assign_game_server(game)
- 1322
game.player_server = NextServer.new(match).player
- 1322
if game.player_server.nil?
- 3
raise Exceptions::InvalidOperation, 'Next player server unknown'
end
end
end
# Class to determine which player serves next
- 1
class NextServer
# * *Args*
# - +match+ -> Match
- 1
def initialize(match)
- 1322
@match = match
end
# Determine the serving player. In a singles match, serving alternates between
# the two opponent players. In doubles, serving alternates between each opponent team and
# the players on each team.
# * *Returns* : Player
- 1
def player
# Count total games, ignoring tiebreaks
- 1322
games_played = match.set_games.all.reduce(0) do |sum, game|
- 10274
sum + (game.tiebreak? ? 0 : 1)
end
- 1322
if match.doubles
- 367
next_doubles_server games_played
else
- 955
next_singles_server games_played
end
end
- 1
private
- 1
attr_reader :match
- 1
def next_doubles_server(games_played)
- 367
case games_played % 4
when 0
- 116
match.first_player_server
when 1
- 98
match.second_player_server
when 2
- 78
other_player_on_team match.first_player_server
when 3
- 75
other_player_on_team match.second_player_server
end
end
- 1
def other_player_on_team(player)
- 153
team = match.team_of_player player
- 153
if team
- 153
if team.first_player == player
- 150
team.second_player
else
- 3
team.first_player
end
end
end
- 1
def next_singles_server(games_played)
- 955
if games_played.even?
- 498
match.first_player_server
else
- 457
other_singles_player match.first_player_server
end
end
- 1
def other_singles_player(player)
- 457
first = match.first_player
- 457
if player == first
- 455
match.second_player
else
- 2
first
end
end
end
# Class to help with winning games
- 1
class WinGame
# * *Args*
# - +match+ -> Match
- 1
def initialize(match)
- 159
@match = match
end
# Indicate if a game can be won (a game is in progress)
- 1
def game?
- 1432
win_game_kind? :game
end
# Indicate if a tiebreak game can be won (a tiebreak game is in progress).
- 1
def tiebreak?
- 92
win_game_kind?(:tiebreak)
end
# Indicate if a match tiebreak game can be completed (a match tiebreak is in progress)
- 1
def match_tiebreak?
- 127
win_game_kind?(:match_tiebreak)
end
# Win a tiebreak game at the end of a set
- 1
def tiebreak(team)
- 31
win_game_kind team, :tiebreak
end
# Win a normal game
- 1
def game(team)
- 1271
win_game_kind team, :game
end
# Win a tiebreak game in a match tiebreaker
- 1
def match_tiebreak(team)
- 22
win_game_kind team, :match_tiebreak
end
- 1
private
- 1
attr_reader :match
- 1
def win_game_kind?(kind)
- 1651
current_game_kind kind
end
- 1
def win_game_kind(team, kind)
- 1324
game = current_game_kind kind
- 1324
raise Exceptions::NotFound, 'Game not found' unless game
- 1324
raise Exceptions::InvalidOperation, 'Invalid type' unless team.is_a? Team
- 1324
game.team_winner_id = team.id
- 1324
game
end
- 1
def current_game_kind(kind)
- 2975
result = nil
- 2975
if match.started
- 2927
last_set_var = match.last_set
- 2927
last_game = last_set_var.last_game if last_set_var
- 2927
if last_game && last_game.state == :in_progress
- 2794
if last_set_var.tiebreak?
- 108
result = last_game if kind == :match_tiebreak
elsif last_game.tiebreak?
- 64
result = last_game if kind == :tiebreak
else
- 2622
result = last_game if kind == :game
end
end
end
- 2975
result
end
end
# Class to help with completing a set and the match. A set or the match may be completed when
# there is a winner.
- 1
class CompleteSetAndMatch
# * *Args*
# - +match+ -> Match
- 1
def initialize(match)
- 1324
@match = match
end
# Complete the current set if enough games have been won.
# Then, complete the match if enough sets have been won.
- 1
def complete!
- 1324
complete_set_play! if complete_set?
- 1324
complete_match_tiebreak! if complete_match_tiebreak?
- 1324
complete_play! if complete_play?
end
- 1
private
- 1
attr_reader :match
# Indicate if a set has a winner.
- 1
def complete_set?
- 1441
completed_kind? :set unless match.min_sets_to_play == 1
end
# Indicate if a match tiebreak has a winner.
- 1
def complete_match_tiebreak?
- 1346
completed_kind? :tiebreak
end
- 1
def completed_kind?(kind)
- 2593
last_set_var = match.last_set
- 2593
last_set_finished = last_set_var && last_set_var.compute_team_winner
- 2593
if last_set_finished
- 425
tiebreak = last_set_var.tiebreak?
- 425
(kind == :tiebreak) == tiebreak
end
end
- 1
def complete_set_play!
- 117
if complete_set?
- 117
last_set_var = match.last_set
- 117
last_set_var.team_winner = last_set_var.compute_team_winner
- 117
last_set_var.save!
end
end
- 1
def complete_match_tiebreak!
- 22
if complete_match_tiebreak?
- 22
last_set_var = match.last_set
- 22
last_set_var.team_winner = last_set_var.compute_team_winner
- 22
last_set_var.save!
end
end
- 1
def complete_play!
- 34
if complete_play?
- 34
if match.min_sets_to_play == 1
- 8
last_set_var = match.last_set
- 8
last_set_var.team_winner = last_set_var.compute_team_winner
- 8
last_set_var.save!
end
- 34
match.team_winner = match.compute_team_winner
- 34
match.save!
end
end
# Complete the match.
- 1
def complete_play?
- 1358
if match.compute_team_winner && !match.completed?
- 68
if match.match_sets.count > 1
# If multiple sets, then last set must be in complete state
- 52
match.last_set.completed?
else
- 16
true
end
end
end
end
end
# Validate changes to a Match
- 1
module MatchValidation
# Help validate the changes to match attributes
# * *Args*
# - +match+ -> Match
# * *Params*
# * +:errors+ - hash
# * Errors will be added to this hash
- 1
def self.validate(match, errors)
- 3555
Validator.new(match).validate(errors)
end
- 1
private
# Help validate
# the changes to match attributes
- 1
class Validator
# * *Args*
# - +match+ -> Match
- 1
def initialize(match)
- 3555
@match = match
end
# Check for various error conditions:
# * Both opponents may not be the same team or player
# * The server of the second doubles game must be valid
# * Must not be on the same team as the server of the first
# * The +:scoring+ kind must be known
# * The opponents must be known
# * The attributes must be permitted to change
# * Many attributes can not be changed after the match has started
# * opponents
# * doubles
# * scoring kind
# * *Params*
# * +:errors+ - hash
# * Errors will be added to this hash
- 1
def validate(errors)
- 3555
that_first_and_second_opponents_different(errors)
- 3555
that_player_servers_on_different_teams(errors)
- 3555
that_scoring_is_known(errors)
- 3555
that_can_change_after_start_play(errors)
- 3555
that_teams_or_players_are_present(errors)
end
- 1
private
- 1
attr_reader :match
- 1
def that_first_and_second_opponents_different(errors)
- 3555
if match.doubles
- 1095
that_doubles_teams_different(errors)
- 1095
that_doubles_players_different(errors)
else
- 2460
that_singles_players_different(errors)
end
end
- 1
def that_doubles_teams_different(errors)
- 1095
unless match.first_team.nil? || match.first_team != match.second_team
- 1
errors.add(:second_team, 'must not be the same as first team')
end
end
- 1
def that_doubles_players_different(errors)
- 1095
unless match.first_team == match.second_team || match.first_team.nil? || match.second_team.nil?
- 1086
arr = [match.first_team.first_player, match.first_team.second_player,
match.second_team.first_player, match.second_team.second_player]
- 1086
arr = arr.compact.map(&:id)
# All id's unique?
- 1086
if arr.uniq.length != arr.length
- 2
errors.add(:second_team, 'must not have players from the first team')
end
end
end
- 1
def that_singles_players_different(errors)
unless match.first_player.nil? ||
- 2460
match.first_player != match.second_player
- 1
errors.add(:second_player,
'must not be the same as first player')
end
end
- 1
def that_player_servers_on_different_teams(errors)
- 3555
first = match.team_of_player(match.first_player_server)
- 3555
second = match.team_of_player(match.second_player_server)
- 3555
if first && first == second
- 3
errors.add(:first_server,
'may not be in the same team as second server')
end
end
- 1
def that_scoring_is_known(errors)
- 3555
unless match.scoring.blank?
- 3551
s = [:one_eight_game, :two_six_game_ten_point, :three_six_game]
- 2
errors.add(:scoring,
- 3551
'invalid value') unless s.include? match.scoring.to_sym
end
end
- 1
def that_can_change_after_start_play(errors)
- 3555
if match.started
- 3314
ValidateChangeMatch.new(match).validate(errors)
end
end
- 1
def that_teams_or_players_are_present(errors)
- 3555
fields = if match.doubles
- 1095
[:first_team, :second_team]
else
- 2460
[:first_player, :second_player]
end
- 3555
fields.each do |sym|
- 7110
value = match.send(sym)
- 7110
errors.add sym, 'can\'t be blank' unless value
- 7110
if value && sym.to_s.end_with?('team')
- 2182
errors.add sym, 'must be doubles team' unless value.doubles
end
end
end
# Class to validate changes to a match after play has started.
# The match opponents may not be changed, for example.
- 1
class ValidateChangeMatch
# * *Args*
# - +match+ -> Match
- 1
def initialize(match)
- 3314
@match = match
end
# Check for invalid changes to the match. Add error messages to a hash.
# * *Args*
# - +errors+ -> hash
- 1
def validate(errors)
- 3314
find_invalid_changes do |sym|
- 10
errors.add sym, 'can\'t be changed after match has started'
end
end
- 1
private
- 1
attr_reader :errors
- 1
attr_reader :match
# Once the match has started, some attributes must not be changed.
- 1
def find_invalid_changes
- 3314
if match.started && !match.started_changed?
- 3144
doubles_var = match.doubles
- 3144
if match.first_team_id_changed?
- 2
yield doubles_var ? :first_team : :first_player
end
- 3144
if match.second_team_id_changed?
- 2
yield doubles_var ? :second_team : :second_player
end
- 3144
yield :doubles if match.doubles_changed?
- 3144
yield :scoring if match.scoring_changed?
- 3147
find_invalid_change_servers { |sym| yield sym }
end
end
- 1
def find_invalid_change_servers
- 3144
first_set_var = match.first_set
if first_set_var &&
(match.first_player_server_id_changed? ||
- 3144
match.second_player_server_id_changed?)
- 182
wins = first_set_var.set_games
.where('team_winner_id IS NOT NULL').count
# find_invalid_change_server_after_wins(wins) { |sym| yield sym }
- 182
if wins >= 1 && match.first_player_server_id_changed?
- 2
yield :first_player_server_id
end
- 182
if match.doubles && wins >= 2 && match.second_player_server_id_changed?
- 1
yield :second_player_server_id
end
end
end
end
end
end
# Model for a tennis match
# == Overview
# * A match may be a singles match or a doubles match
# * A match has two opponent teams:
# * Doubles teams with four players in total
# * Singles teams with two players in total
# * A match has a state:
# * +:not_started+
# * +:in_progress+
# * +:complete+
# * A match may have first servers
# First server are the players that serve
# the first game or two. For a singles match, there is a
# first server for the first game. For a doubles match, there is a first
# server for the first two games; one from each team.
# * A match has sets (see MatchSet). Sets have games (see SetGame).
# * A match has scoring:
# * +:two_six_game_ten_point+
# * +:one_eight_game+
# * +:three_six_game+
# * A complete match has a winner
#
# == Schema Information
#
# Table name: matches
#
# id :integer not null, primary key
# created_at :datetime not null
# updated_at :datetime not null
# first_team_id :integer not null
# second_team_id :integer not null
# scoring :string not null
# started :boolean default(FALSE), not null
# doubles :boolean default(FALSE), not null
# first_player_server_id :integer
# second_player_server_id :integer
# title :string
# team_winner_id :integer
# play_version :integer
#
- 1
class Match < ActiveRecord::Base
- 1
belongs_to :first_player_server,
class_name: 'Player', foreign_key: :first_player_server_id
- 1
belongs_to :second_player_server,
class_name: 'Player', foreign_key: :second_player_server_id
- 1
belongs_to :first_team, class_name: 'Team', foreign_key: :first_team_id
- 1
belongs_to :second_team, class_name: 'Team', foreign_key: :second_team_id
- 1
belongs_to :team_winner, class_name: 'Team', foreign_key: :team_winner_id
- 1
has_many :match_sets, dependent: :destroy
- 1
has_many :set_games, through: :match_sets
# Store nil instead of "". The schema does not allow duplicate titles,
# including duplicate ""
- 3556
before_validation { self.title = nil if self.title.blank? }
# If a title is not provided when match is created, generate one
- 275
before_create { self.title = next_match_title if self.title.blank? }
# Some of these rules mimic constraints in the schema
- 1
validates :scoring, presence: true # schema
- 1
validates_uniqueness_of :title, allow_nil: true # schema
- 1
validate do
- 3555
MatchValidation::validate self, errors
end
# Execute an action on the match, such as +:win_game+
#
# * *Args* :
# - +action+ -> the action to execute
# - +params+ -> hash of action parameters
# * *Raises* :
# - +UnknownOperation+ -> if the action is unknown
# - +InvalidOperation+ -> if the action is not permitted
#
# === Action
# * +:start_play+ - Start playing
# * +:discard_play+ - Discard all scoring
# * +:start_set+ - Start the next set
# * +:start_game+ - Start the next game
# When starting the first game in a singles match, or the first
# two games in a doubles match, +params+ identifies the player to
# serve the game.
# * +:start_tiebreak+ - Start game tiebreak
# * +:remove_last_change+ - Back up to the previous state
# * +:start_match_tiebreak+ - Start the match tiebreak
# * +:win_game+ - Win the current game
# * +:win_tiebreak+ - Win the current set tiebreak.
# * +:win_match_tiebreak+ - Win the current match tiebreak.
# For the 3 +:win_*+ actions, +params+ identifies the
# doubles team or singles team to win
# === Params
# * +:version+ Version number from client.
# If provided, it will be compared to the
# current match play version number. An exception will be
# raised if the values are not equal. The purpose of the version
# number is to detect when a client is out of sync.
# * +:opponent+ - Team or Player
# This options is used with +:win_*+ actions to identify the winning team or player. It is also
# used with the +:start_game+ action to identify the serving player
#
- 1
def play_match!(action, params = nil)
- 2927
method = play_actions.lookup_method(action)
- 2927
if method
- 2925
ActiveRecord::Base.transaction do
- 2925
version = params[:version] if params
- 2925
version = version.to_i if version
- 2925
if version && version != self.play_version
- 4
raise Exceptions::InvalidOperation,
"Unable to #{action.to_s.tr('_', ' ')}. " +
if version < self.play_version
- 2
'Client is out of sync.'
else
- 2
'Unexpected match version number.'
end
end
- 2921
method[:exec].call params
# Version number is used to detect when client has is out of sync
- 2898
self.play_version = next_version_number
- 2898
self.save!
end
else
- 2
raise Exceptions::UnknownOperation, "Unknown action: #{action}"
end
end
# Indicate if an action can be executed.
# +:win_game+ can be executed if a game has started, for example.
- 1
def play_match?(action)
- 3129
methods = play_actions.lookup_method(action)
- 3129
if methods
- 3128
methods[:query].call
else
- 1
raise Exceptions::UnknownOperation, "Unknown action: #{action}"
end
end
# Generate a hash of valid actions
# * *Returns* : Hash
# === Example
# {
# start_game: true,
# discard_play: true,
# remove_last_change: true
# }
- 1
def valid_actions
- 54
play_actions.valid_actions
end
# Indicate if the match is completed.
- 1
def completed?
- 2168
team_winner
end
# Get the first player opponent in a singles match
# * *Returns* : Player
- 1
def first_player
- 7959
singles_player_of_team first_team
end
# Set the first player opponent in a singles match.
# A new team may be created to hold the player
- 1
def first_player=(player)
- 193
self.first_team = singles_player_team(player)
end
# Get the second player opponent in a singles match
# * *Returns* : Player
- 1
def second_player
- 5399
singles_player_of_team second_team
end
# Set the second player opponent in a singles match.
# A new team may be created to hold the player
- 1
def second_player=(player)
- 192
self.second_team = singles_player_team(player)
end
# Get the state of the match
# * :complete
# * :in_progress
# * :not_started
- 1
def state
- 681
if completed?
- 43
:complete
- 638
elsif started
- 534
:in_progress
else
- 104
:not_started
end
end
# Calculate the number of sets won by a team
#
# * *Args* :
# - +team+ -> Team
# * *Returns* : Integer
- 1
def sets_won(team)
- 2729
match_sets.reduce(0) do |sum, set|
- 3889
winner = set.compute_team_winner
- 3889
sum + (winner && winner == team ? 1 : 0)
end
end
# Get the first set of the match
# * *Returns* : MatchSet
- 1
def first_set
- 3144
match_sets.first
end
# Get the last set of the match
# * *Returns* : MatchSet
- 1
def last_set
- 14561
match_sets.last
end
# Get the minimum number of sets that will be played in this match
# The minimum is 2 for +:three_six_game+ scoring, for example
# * *Returns* : Integer
- 1
def min_sets_to_play
- 4204
max = max_sets_to_play
- 4204
raise Exceptions::ApplicationError, 'Unexpected game count' if max.even?
- 4204
(max + 1) / 2
end
# Get the maximum number of sets that will be played in this match.
# The maximum is 3 for +:three_six_game+ scoring, for example
# * *Returns* : Integer
- 1
def max_sets_to_play
- 4204
case scoring.to_sym
when :two_six_game_ten_point
- 2998
3
when :one_eight_game
- 610
1
else # :three_six_game
- 596
3
end
end
# Get the ordinal of the next game to play in the
# match. The lowest ordinal is 1.
# * *Returns* : Integer
- 1
def next_game_ordinal
- 1393
last_set ? last_set.set_games.count + 1 : 0
end
# Indicate whether a set is a match tiebreak
# * *Args* :
# - +ordinal+ -> Integer
# * *Returns* : Boolean
- 1
def tiebreak_set?(ordinal)
- 246
scoring_of_set(ordinal) == :ten_point
end
# Compute the winner of the match based on
# the number of sets won by each opponent
# * *Returns* : Team or nil
- 1
def compute_team_winner
- 1393
if completed?
- 1
team_winner
else
- 1392
if first_team && second_team
- 1392
if sets_won(first_team) == min_sets_to_play
- 81
first_team
- 1311
elsif sets_won(second_team) == min_sets_to_play
- 21
second_team
end
end
end
end
# Determine whether a Team can win the match by
# winning one more game
# * *Args* :
# - +team+ -> Team
# * *Returns* : Boolean
- 1
def near_team_winner?(team)
- 26
unless completed?
- 26
sets_won(team) + 1 == min_sets_to_play
end
end
# Get the scoring of a set
# * *Args* :
# - +ordinal+ -> set ordinal
# * *Returns* : symbol
# * +:six_game+
# * +:eight_game+
# * +:ten_point+ (tiebreak)
- 1
def scoring_of_set(ordinal)
- 517
case scoring.to_sym
when :two_six_game_ten_point
- 364
ordinal == 3 ? :ten_point : :six_game
when :one_eight_game
- 83
:eight_game
else # :three_six_game
- 70
:six_game
end
end
# Get the opponent team that includes a particular player
# * *Args* :
# - +player+ -> Player
# * *Returns* : Team
- 1
def team_of_player(player)
- 7263
if player
- 3840
if first_team.include_player?(player)
- 3027
first_team
- 813
elsif second_team.include_player?(player)
- 813
second_team
end
end
end
- 1
private
- 1
def singles_player_of_team(team)
- 13358
raise Exceptions::InvalidOperation, 'singles expected' if doubles
- 13358
team.first_player if team && !team.doubles
end
- 1
def singles_player_team(player)
- 385
raise Exceptions::InvalidOperation, 'singles expected' if doubles
- 385
player.singles_team! if player # force a team
end
# Generate a unique title for a match
- 1
def next_match_title
- 4
"Match#{next_match_number}"
end
# Get a unique number to use when generating a match title
- 1
def next_match_number
# This returns a PGresult object
# [http://rubydoc.info/github/ged/ruby-pg/master/PGresult]
- 4
result = Match.connection.execute("SELECT nextval('match_number_seq')")
- 4
result[0]['nextval']
end
# A Postgres sequence generates a version number for a match score
- 1
def next_version_number
# This returns a PGresult object
# [http://rubydoc.info/github/ged/ruby-pg/master/PGresult]
- 2898
result = Match.connection.execute("SELECT nextval('play_version_seq')")
- 2898
result[0]['nextval']
end
- 1
def play_actions
- 6110
@play_actions ||= MatchPlay::PlayActions.new(self)
end
end
# Model for a set in a match
# == Overview
# * A set belongs to a Match
#
# * A set has a state:
# * +:in_progress+
# * +:complete+
#
# * A set has a scoring kind
# * +:eight_game+
# * +:ten_point+
# * +:six_game+
#
# +:ten_point+ indicate a match tiebreak set.
# A tiebreak set has only one game
#
# * A set has an ordinal.
# The first set in the match has ordinal 1
#
# * A complete set has a winning team
#
# == Schema Information
#
# Table name: match_sets
#
# id :integer not null, primary key
# match_id :integer not null
# ordinal :integer not null
# created_at :datetime not null
# updated_at :datetime not null
# scoring :string not null
# team_winner_id :integer
#
- 1
class MatchSet < ActiveRecord::Base
- 1
has_many :set_games, dependent: :destroy
- 1
belongs_to :first_player_server,
class_name: 'Player', foreign_key: :first_player_server_id
- 1
belongs_to :second_player_server,
class_name: 'Player', foreign_key: :second_player_server_id
- 1
belongs_to :team_winner,
class_name: 'Team', foreign_key: :team_winner_id
- 1
belongs_to :match
- 1
validates :ordinal, :scoring, :match, presence: true
- 1
validate :that_scoring_is_known
- 7386
default_scope { order('ordinal ASC') }
# Indicate whether the match has a winner
# * *Returns* : Boolean
- 1
def completed?
- 11254
team_winner
end
# Get the state of the match
# * *Returns* : state
# * +:in_progress+
# * +:complete+
- 1
def state
- 4485
if completed?
- 304
:complete
else
- 4181
:in_progress
end
end
# Compute the winner of the set, if any, based
# on the games won and the scoring kind
# * *Returns* : Team or nil
- 1
def compute_team_winner
- 6629
if completed?
- 1610
team_winner
else
- 5019
lookup = lookup_games_won
# pass games won by each team
- 5019
calc_winner_team(lookup[match.first_team_id][0],
lookup[match.second_team_id][0])
end
end
# Indicate whether a team can win the set if the team
# wins one more game
# * *Args* :
# - +team+ -> Team
# * *Returns* : Boolean
- 1
def near_team_winner?(team)
- 88
unless completed?
- 72
lookup = lookup_games_won
- 72
first_won = lookup[match.first_team_id][0]
- 72
second_won = lookup[match.second_team_id][0]
- 72
first_won += 1 if team.id == match.first_team_id
- 72
second_won += 1 if team.id == match.second_team_id
- 72
calc_winner_team(first_won, second_won)
end
end
# Get the last game of the set
# * *Returns* : Game
- 1
def last_game
- 2971
set_games.last
end
# Get the minimum number of games that an opponent
# must win in order to win the set, based on the scoring
# kind.
# * *Returns* : count
- 1
def win_threshold
- 17588
case scoring.to_sym
when :eight_game
- 2501
8
when :ten_point
- 124
1
else # :six_game
- 14963
6
end
end
# Indicate if this set is a match tiebreak
# * *Returns* : Boolean
- 1
def tiebreak?
- 3238
scoring.to_sym == :ten_point
end
# Indicate whether a particular game is a tiebreak.
# A game may be a tiebreak because the set is tied
# (e.g. 6-6), or because the set is a match tiebreak.
# * *Args* :
# - +game_ordinal+ -> ordinal
# * *Returns* : Boolean
- 1
def tiebreak_game?(game_ordinal)
- 3762
tiebreak_ordinal = (win_threshold == 1) ? 1 : win_threshold * 2 + 1
- 3762
game_ordinal == tiebreak_ordinal
end
# Clear cached scores. For internal use
- 1
def score_changed
- 4118
@games_won_by_team = nil
end
- 1
private
- 1
def that_scoring_is_known
- 706
unless scoring.blank?
- 705
s = [:six_game, :eight_game, :ten_point]
- 705
errors.add(:scoring, 'invalid value') unless s.include? scoring.to_sym
end
end
- 1
def calc_winner_team(first_team_wins, second_team_wins)
- 5091
won = if win_threshold >= 2
- 4967
calc_winner_by_games(first_team_wins, second_team_wins)
else
- 124
[first_team_wins, second_team_wins].max == 1
end
- 5091
if won
- 503
first_team_wins > second_team_wins ? match.first_team : match.second_team
end
end
- 1
def calc_winner_by_games(first_team_wins, second_team_wins)
- 4967
threshold = win_threshold
- 4967
minmax = [first_team_wins, second_team_wins].minmax
- 4967
win_by_two = (first_team_wins - second_team_wins).abs >= 2 &&
minmax[1] >= threshold
- 4967
win_tiebreak = minmax == [threshold, threshold + 1]
- 4967
win_by_two || win_tiebreak
end
- 1
def lookup_games_won
- 5091
if @games_won_by_team
# cache for slight performance improvement
- 3557
return @games_won_by_team
end
- 1534
lookup = {}
- 1534
sum_wins do |first_wins, second_wins|
- 1534
lookup = { match.first_team_id => first_wins,
match.second_team_id => second_wins }
end
- 1534
@games_won_by_team = lookup
- 1534
lookup
end
- 1
def sum_wins
- 1534
first_wins = [0]
- 1534
second_wins = [0]
- 1534
sum_set_games do |sum1, sum2|
- 7373
first_wins << sum1
- 7373
second_wins << sum2
end
- 1534
first_wins[0] = first_wins.last
- 1534
second_wins[0] = second_wins.last
- 1534
yield first_wins, second_wins
end
- 1
def sum_set_games
- 1534
sum1 = sum2 = 0
- 1534
set_games.each do |game|
- 7393
break unless game.team_winner
- 7373
if game.team_winner == match.first_team
- 4005
sum1 += 1
else
- 3368
sum2 += 1
end
- 7373
yield sum1, sum2
end
end
end
# Model for a player
# == Overview
# * A player may be on a team
# * A player may be the first or second server in a match
# * A player may be the server of a game
#
# == Schema Information
#
# Table name: players
#
# id :integer not null, primary key
# name :string not null
# created_at :datetime not null
# updated_at :datetime not null
#
- 1
class Player < ActiveRecord::Base
- 1
validates :name, presence: true
- 1
validates_uniqueness_of :name
- 1
before_destroy :that_can_destroy
# Find the singles team associated with this player
# * *Returns* : Team or nil
- 1
def singles_team
- 420
Team.find_by_doubles_and_first_player_id(false, self.id)
end
# Force a player to have an associated singles team. Creates a new
# Team if needed.
# * *Returns* : Team
- 1
def singles_team!
- 405
team = singles_team
- 405
unless team
- 398
team = Team.new
- 398
team.doubles = false
- 398
team.first_player = self
- 398
team.save!
end
- 405
team
end
- 1
private
- 1
def that_can_destroy
- 9
clean_singles_team
- 9
if on_any_team?
- 6
errors.add :errors, 'Can\'t delete a player in a match or on a team'
- 6
false
end
end
- 1
def on_any_team?
- 9
Team.where('first_player_id=? OR second_player_id=?', id, id).exists?
end
- 1
def clean_singles_team
- 9
team = singles_team
- 9
if team
unless Match.where('first_team_id=? OR second_team_id=?',
- 2
team.id, team.id).exists?
- 1
team.destroy!
end
end
end
end
# Model for a game
#
# == Overview
#
# * A game belongs to a MatchSet
# * A game has a state:
# * +:in_progress+
# * +:finished+
# * A game has an ordinal. The first game in the set has ordinal 1
# * A game may have a winning team
# * A game may be a tiebreak
# A tiebreak is a special kind of game that occurs at the end of a set
# * A non-tiebreak game has a serving player
#
# == Schema Information
#
# Table name: set_games
#
# id :integer not null, primary key
# ordinal :integer not null
# match_set_id :integer not null
# team_winner_id :integer
# created_at :datetime not null
# updated_at :datetime not null
# player_server_id :integer
# tiebreaker :boolean default(FALSE), not null
#
- 1
class SetGame < ActiveRecord::Base
- 1
belongs_to :match_set
- 1
belongs_to :team_winner, class_name: 'Team', foreign_key: :team_winner_id
- 1
belongs_to :player_server,
class_name: 'Player', foreign_key: :player_server_id
- 1
validates :ordinal, :match_set, presence: true
- 11334
default_scope { order('ordinal ASC') }
- 4110
after_save { match_set.score_changed }
- 10
after_destroy { match_set.score_changed }
# Get the state of a game
# * *Returns* : symbol
# * +:in_progress+ or
# * +:finished+
- 1
def state
- 6485
team_winner_id ? :finished : :in_progress
end
# Indicate if the game is a tiebreak
# * *Returns* : Boolean
- 1
def tiebreak?
- 14753
tiebreaker
end
end
# Model for a team
#
# == Overview
#
# * A team may be an opponent in a match
# * A team may be the winner of a game, a match or a set
# * A doubles team has two players
# * A singles team has one player
# Singles teams are created as needed to allow a
# player to be an opponent in a match.
#
# == Schema Information
#
# Table name: teams
#
# id :integer not null, primary key
# name :string
# first_player_id :integer not null
# second_player_id :integer
# created_at :datetime not null
# updated_at :datetime not null
# doubles :boolean default(FALSE), not null
#
- 1
class Team < ActiveRecord::Base
- 1
belongs_to :first_player,
class_name: 'Player', foreign_key: :first_player_id
- 1
belongs_to :second_player,
class_name: 'Player', foreign_key: :second_player_id
- 745
before_validation { self.name = nil if self.name.blank? }
# redundant
# validates :first_player_id, presence: true
- 1
validates_uniqueness_of :name, allow_nil: true
- 1
validate :that_is_valid_first_player
- 1
validate :that_is_valid_second_player
- 1
validate :that_is_unique_player_pair
- 1
validate :that_can_change_player
- 1
before_destroy :that_can_destroy_team
# If a name is not provided when match is created, generate name
- 719
before_create { self.name = next_team_name if self.doubles && self.name.blank? }
# Indicate whether the team includes all players in a list
# * *Args* :
# - +players+ -> array of players
# * *Returns* : Boolean
- 1
def include_players?(players)
- 4653
players == [first_player, second_player] & players.compact
end
# Indicate whether the team includes a particular player
# * *Args* :
# - +player+ -> Player
# * *Returns* : Boolean
- 1
def include_player?(player)
- 4653
include_players?([player])
end
- 1
private
- 1
def that_can_destroy_team
- 6
if team_in_match?
- 3
errors.add :errors, 'Can\'t delete a team in a match'
- 3
false # discard destroy
end
end
- 1
def that_can_change_player
- 744
first_changed = first_player_id_changed?
- 744
second_changed = second_player_id_changed?
- 744
if (first_changed || second_changed) and team_playing_match?
- 2
message = 'can\'t be changed when in a match that has started'
- 2
errors.add :first_player, message if first_changed
- 2
errors.add :second_player, message if second_changed
end
end
- 1
def team_playing_match?
- 738
matches_of_team.where('started').exists?
end
- 1
def team_in_match?
- 6
matches_of_team.exists?
end
- 1
def matches_of_team
- 744
Match.where('first_team_id=? OR second_team_id=?', id, id)
end
- 1
def that_is_valid_first_player
- 744
if first_player.nil?
- 6
errors.add(:first_player, if first_player_id.blank?
- 3
'can\'t be blank'
else
- 3
'not found'
end)
end
end
- 1
def that_is_valid_second_player
- 744
if doubles
- 346
if second_player.nil?
- 6
errors.add(:second_player, if second_player_id.blank?
- 3
'must be specified'
else
- 3
'not found'
end)
else
- 340
errors.add(:second_player, 'must not be the same as first player') if second_player == first_player
end
else
# not doubles
- 398
errors.add(:second_player, 'not allowed') if second_player
end
end
- 1
def that_is_unique_player_pair
- 744
if doubles && first_player_id && second_player_id
- 340
player_ids = [first_player_id, second_player_id]
- 340
existing_team = Team.where(first_player_id: player_ids)
.where(second_player_id: player_ids).first
- 340
unless existing_team.nil? || existing_team.id == self.id
- 3
errors.add(:error, 'A team with these players already exists')
end
end
end
# Generate a unique title for a team
- 1
def next_team_name
- 2
"Team#{next_team_number}"
end
# Get a unique number to use when generating a team title
- 1
def next_team_number
# This returns a PGresult object
# [http://rubydoc.info/github/ged/ruby-pg/master/PGresult]
- 2
result = Team.connection.execute("SELECT nextval('team_number_seq')")
- 2
result[0]['nextval']
end
end
# Model for a user
#
# == Overview
#
# A user has a name, password and authentication token
#
# == Schema Information
#
# Table name: users
#
# id :integer not null, primary key
# username :string default(""), not null
# encrypted_password :string default(""), not null
# reset_password_token :string
# reset_password_sent_at :datetime
# remember_created_at :datetime
# sign_in_count :integer default(0), not null
# current_sign_in_at :datetime
# last_sign_in_at :datetime
# current_sign_in_ip :string
# last_sign_in_ip :string
# created_at :datetime
# updated_at :datetime
# auth_token :string default("")
#
- 1
class User < ActiveRecord::Base
- 1
validates :auth_token, uniqueness: true
- 1
validates :username, uniqueness: true
- 1
validates :username, presence: true
- 1
devise :database_authenticatable
- 1
before_create :generate_authentication_token!
# Set auth_token attribute to a new value
- 1
def generate_authentication_token!
begin
- 132
self.auth_token = Devise.friendly_token
- 132
end while self.class.exists?(auth_token: auth_token)
end
end
# Base class for this application's serializers.
# Disable JSON root object
- 1
class V1::ApplicationArraySerializer < ActiveModel::ArraySerializer
- 1
self.root = false
end
# Base class for this application's serializers.
# Disable JSON root object
- 1
class V1::ApplicationSerializer < ActiveModel::Serializer
- 1
self.root = false
end
# Serialize a Match score with the following information:
# * Attributes of the match (See V1::MatchSerializer)
# * +:id+
# * +:title+
# * +:scoring+
# * +:doubles+
# * +:state+
# * +:winner+
# * Attributes related to keeping score
# * +:sets+ - The sets in the match. See V1::MatchSetSerializer.
# * +:actions+ - The actions that may be executed on the match
# * +:errors+ - Errors that occurred when executing an action
# * +:server+ - The Player id's that may serve the next game
# * +:near_winner+
# * The Team or Player id's that are near to winning a set
# * The Team or Player id's that are near to winning the match
#
- 1
class V1::MatchScoreboardSerializer < V1::MatchSerializer
- 1
attributes :id, :title, :scoring, :doubles, :state, :winner, :sets, :actions, :errors, :servers,
:near_winners
# Serialize the sets of the match. See
# V1::MatchSetSerializer
#
# * *Returns* :
# - array of serialized sets
#
- 1
def sets
- 53
V1::ApplicationArraySerializer.new(object.match_sets, each_serializer: V1::MatchSetSerializer)
end
# Serialize actions that may be executed on the match.
# See Match.valid_actions
# * *Returns* : Hash
# === Example
# {
# start_game: true,
# discard_play: true,
# remove_last_change: true
# }
#
- 1
def actions
- 53
object.valid_actions
end
# Serialize the id's of teams or players that are near to winning a set
# or the match. Player id's are serialized for singles matches;
# Team id's for doubles matches.
#
# * *Returns* : Hash
# === Example
# (
# set: [10],
# match[]
# }
#
- 1
def near_winners
- 53
result = {
set: [],
match: []
}
- 53
first = object.first_team
- 53
second = object.second_team
- 53
last_set_var = object.last_set
- 53
if first && second && last_set_var
- 38
add_near_winner first, last_set_var, result
- 38
add_near_winner second, last_set_var, result
end
- 53
result
end
# Serialize a list of player id's that may serve the first or second game.
# (One of these players must be passed as a parameter when executing the
# +:start_game+ action)
#
# * *Returns* :
# - an array of player ids
#
# * Example list before starting a doubles match:
#
# \[10, 20, 30, 40\]
#
# * Example list before starting the second game of a doubles match:
#
# \[30, 40\]
#
# * Example list before starting the first game of a singles match:
#
# \[10, 20\]
#
# * Value for all other cases:
#
# \[\]
#
- 1
def servers
- 53
result = []
- 53
result.push(object.first_player_server.id) if object.first_player_server
- 53
result.push(object.second_player_server.id) if object.second_player_server
- 53
result
end
- 1
private
- 1
def add_near_winner(team, set, result)
- 76
if set.near_team_winner? team
- 16
id = opponent_id team
- 16
result[:set].push id
- 16
if object.near_team_winner? team
- 16
result[:match].push id
end
end
end
end
# Serialize a Match with the following attributes:
# * +:id+
# * +:title+
# * +:scoring+ kind
# * +:doubles+
# * +:state+
# * +:winner+
# * +:first_team+ (when doubles)
# * +:second_team+ (when doubles)
# * +:first_player+ (when not doubles)
# * +:second_player+ (when not doubles)
#
- 1
class V1::MatchSerializer < V1::ApplicationSerializer
- 1
attributes :id, :title, :scoring, :doubles, :state, :winner
# Serialize the player or team id of the match winner,
# if any.
#
# * *Returns* : id
#
- 1
def winner
- 148
if object.team_winner.nil?
nil
else
- 22
opponent_id object.team_winner
end
end
# Indicate dynamic attributes to the serializer. For a doubles match,
# serialize first and second players. For a singles match, serialize the first
# and second teams.
#
# * *Returns* : Hash of attributes
#
- 1
def attributes
- 95
data = super
- 95
if object.doubles
- 48
data[:first_team] = first_team
- 48
data[:second_team] = second_team
else
- 47
data[:first_player] = first_player
- 47
data[:second_player] = second_player
end
- 95
data
end
# Serialize the first player
#
# * *Returns* : V1::PlayerSerializer
#
- 1
def first_player
- 47
V1::PlayerSerializer.new(object.first_team.first_player)
end
# Serialize the first team
#
# * *Returns* : V1::TeamSerializer
#
- 1
def first_team
- 48
V1::TeamSerializer.new(object.first_team)
end
# Serialize the second player
#
# * *Returns* : See V1::PlayerSerializer
#
- 1
def second_player
- 47
V1::PlayerSerializer.new(object.second_team.first_player)
end
# Serialize the second team. See V1::OpponentTeamSerializer.
#
# * *Returns* : V1::TeamSerializer
#
- 1
def second_team
- 48
V1::TeamSerializer.new(object.second_team)
end
- 1
protected
# Get the id for an opponent in a match.
# For a doubles match, get a team id. For a singles match,
# get a player id
# * *Args* :
# - +team+ -> opponent Team
# * *Returns* : id
- 1
def opponent_id(team)
- 38
if object.doubles
- 19
team.id
else
- 19
team.first_player.id
end
end
end
# Serialize a MatchSet with the following attributes:
# * +:scoring+
# * +:state+
# * +:winner+
# * +:games+
#
- 1
class V1::MatchSetSerializer < V1::ApplicationSerializer
- 1
attributes :scoring, :winner, :games, :state
# Serialize the games of the set.
#
# * *Returns* :
# - array of V1::SetGameSerializer
#
- 1
def games
- 70
V1::ApplicationArraySerializer.new(object.set_games, each_serializer: V1::SetGameSerializer)
end
# Serialize the Player or Team id of the winner
#
# * *Returns* : Player id or Team id or nil
#
- 1
def winner
- 70
if object.team_winner.nil?
nil
else
- 40
if object.match.doubles
- 20
object.team_winner.id
else
- 20
object.team_winner.first_player.id
end
end
end
end
# Serializes a Player with the following attributes:
# * +:id+
# * +:name+
#
- 1
class V1::PlayerSerializer < V1::ApplicationSerializer
- 1
attributes :id, :name
end
# Serializes a logged in User. The following attributes
# are serialized:
# * +:id+
# * +:username+
# * +:auth_token+
#
- 1
class V1::SessionSerializer < V1::ApplicationSerializer
- 1
attributes :id, :username, :auth_token
end
# Serializes a SetGame with the following attributes:
# * +:winner+ - Id of a Team or a Player
# * +:server+ - Id of a Player
# * +:tiebreak+ - true if this game is a tiebreak
#
- 1
class V1::SetGameSerializer < V1::ApplicationSerializer
- 1
attributes :winner, :server, :tiebreak
# Serialize the Player or Team id of the game winner
#
# * *Returns* : Team id or Player id or nil
#
- 1
def winner
- 400
if object.team_winner.nil?
nil
else
- 384
if object.match_set.match.doubles
- 192
object.team_winner.id
else
- 192
object.team_winner.first_player.id
end
end
end
# Indicate a tiebreak game
#
# * *Returns* : true or nil
#
- 1
def tiebreak
- 400
object.tiebreak? ? true : nil
end
# Serialize the Player id of the game server.
#
# * *Returns* : Player id or nil
#
- 1
def server
- 400
object.player_server_id
end
end
# Serializes a Team with the following attributes:
# * +:id+
# * +:first_player+
# * +:second_player+
#
- 1
class V1::TeamSerializer < V1::ApplicationSerializer
- 1
attributes :id, :name, :first_player, :second_player
# Serialize the first player on the team
# * *Returns* : V1::PlayerSerializer
- 1
def first_player
- 118
V1::PlayerSerializer.new(object.first_player)
end
# Serialize the second player on the team
# * *Returns* : V1::PlayerSerializer
- 1
def second_player
- 118
V1::PlayerSerializer.new(object.second_player)
end
end
# Serializes the current User with the following attributes:
# * +:id+
# * +:username+
#
- 1
class V1::UserSerializer < V1::ApplicationSerializer
- 1
attributes :id, :username
end
- 1
require File.expand_path('../boot', __FILE__)
- 1
require 'rails/all'
# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
- 1
Bundler.require(*Rails.groups)
- 1
module TennisNg
- 1
class Application < Rails::Application
# Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded.
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
# Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
# config.time_zone = 'Central Time (US & Canada)'
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
# config.i18n.default_locale = :de
# Do not swallow errors in after_commit/after_rollback callbacks.
- 1
config.active_record.raise_in_transactional_callbacks = true
end
end
- 1
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
- 1
require 'bundler/setup' # Set up gems listed in the Gemfile.
# Load the Rails application.
- 1
require File.expand_path('../application', __FILE__)
# Initialize the Rails application.
- 1
Rails.application.initialize!
- 1
Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb.
# The test environment is used exclusively to run your application's
# test suite. You never need to work with it otherwise. Remember that
# your test database is "scratch space" for the test suite and is wiped
# and recreated between test runs. Don't rely on the data there!
- 1
config.cache_classes = true
# Do not eager load code on boot. This avoids loading your whole application
# just for the purpose of running a single test. If you are using a tool that
# preloads Rails for running tests, you may have to set it to true.
- 1
config.eager_load = false
# Configure static file server for tests with Cache-Control for performance.
- 1
config.serve_static_files = true
- 1
config.static_cache_control = 'public, max-age=3600'
# Show full error reports and disable caching.
- 1
config.consider_all_requests_local = true
- 1
config.action_controller.perform_caching = false
# Raise exceptions instead of rendering exception templates.
- 1
config.action_dispatch.show_exceptions = false
# Disable request forgery protection in test environment.
- 1
config.action_controller.allow_forgery_protection = false
# Tell Action Mailer not to deliver emails to the real world.
# The :test delivery method accumulates sent emails in the
# ActionMailer::Base.deliveries array.
- 1
config.action_mailer.delivery_method = :test
# Randomize the order test cases are executed.
- 1
config.active_support.test_order = :random
# Print deprecation notices to the stderr.
- 1
config.active_support.deprecation = :stderr
# Raises error for missing translations
# config.action_view.raise_on_missing_translations = true
end
# Use this hook to configure devise mailer, warden hooks and so forth.
# Many of these configuration options can be set straight in your model.
- 1
Devise.setup do |config|
# The secret key used by Devise. Devise uses this key to generate
# random tokens. Changing this key will render invalid all existing
# confirmation, reset password and unlock tokens in the database.
# config.secret_key = '12c3a022d553cbe4b214d7fb246a9ae6e8b16d6a3b0faaea71a9da8176e4200e762423e50f9d7ac51797f39b91d4fe71d513bfadbae777aefaa95b655b3aa491'
# ==> Mailer Configuration
# Configure the e-mail address which will be shown in Devise::Mailer,
# note that it will be overwritten if you use your own mailer class
# with default "from" parameter.
- 1
config.mailer_sender = 'please-change-me-at-config-initializers-devise@example.com'
# Configure the class responsible to send e-mails.
# config.mailer = 'Devise::Mailer'
# ==> ORM configuration
# Load and configure the ORM. Supports :active_record (default) and
# :mongoid (bson_ext recommended) by default. Other ORMs may be
# available as additional gems.
- 1
require 'devise/orm/active_record'
# ==> Configuration for any authentication mechanism
# Configure which keys are used when authenticating a user. The default is
# just :email. You can configure it to use [:username, :subdomain], so for
# authenticating a user, both parameters are required. Remember that those
# parameters are used only when authenticating and not when retrieving from
# session. If you need permissions, you should implement that in a before filter.
# You can also supply a hash where the value is a boolean determining whether
# or not authentication should be aborted when the value is not present.
# config.authentication_keys = [ :email ]
- 1
config.authentication_keys = [:username]
# Configure parameters from the request object used for authentication. Each entry
# given should be a request method and it will automatically be passed to the
# find_for_authentication method and considered in your model lookup. For instance,
# if you set :request_keys to [:subdomain], :subdomain will be used on authentication.
# The same considerations mentioned for authentication_keys also apply to request_keys.
# config.request_keys = []
# Configure which authentication keys should be case-insensitive.
# These keys will be downcased upon creating or modifying a user and when used
# to authenticate or find a user. Default is :email.
# config.case_insensitive_keys = [ :email ]
- 1
config.case_insensitive_keys = [ :username ]
# Configure which authentication keys should have whitespace stripped.
# These keys will have whitespace before and after removed upon creating or
# modifying a user and when used to authenticate or find a user. Default is :email.
# config.strip_whitespace_keys = [ :email ]
- 1
config.strip_whitespace_keys = [ :username ]
# Tell if authentication through request.params is enabled. True by default.
# It can be set to an array that will enable params authentication only for the
# given strategies, for example, `config.params_authenticatable = [:database]` will
# enable it only for database (email + password) authentication.
# config.params_authenticatable = true
# Tell if authentication through HTTP Auth is enabled. False by default.
# It can be set to an array that will enable http authentication only for the
# given strategies, for example, `config.http_authenticatable = [:database]` will
# enable it only for database authentication. The supported strategies are:
# :database = Support basic authentication with authentication key + password
# config.http_authenticatable = false
# If http headers should be returned for AJAX requests. True by default.
# config.http_authenticatable_on_xhr = true
# The realm used in Http Basic Authentication. 'Application' by default.
# config.http_authentication_realm = 'Application'
# It will change confirmation, password recovery and other workflows
# to behave the same regardless if the e-mail provided was right or wrong.
# Does not affect registerable.
# config.paranoid = true
# By default Devise will store the user in session. You can skip storage for
# particular strategies by setting this option.
# Notice that if you are skipping storage for all authentication paths, you
# may want to disable generating routes to Devise's sessions controller by
# passing skip: :sessions to `devise_for` in your config/routes.rb
- 1
config.skip_session_storage = [:http_auth]
# By default, Devise cleans up the CSRF token on authentication to
# avoid CSRF token fixation attacks. This means that, when using AJAX
# requests for sign in and sign up, you need to get a new CSRF token
# from the server. You can disable this option at your own risk.
# config.clean_up_csrf_token_on_authentication = true
# ==> Configuration for :database_authenticatable
# For bcrypt, this is the cost for hashing the password and defaults to 10. If
# using other encryptors, it sets how many times you want the password re-encrypted.
#
# Limiting the stretches to just one in testing will increase the performance of
# your test suite dramatically. However, it is STRONGLY RECOMMENDED to not use
# a value less than 10 in other environments. Note that, for bcrypt (the default
# encryptor), the cost increases exponentially with the number of stretches (e.g.
# a value of 20 is already extremely slow: approx. 60 seconds for 1 calculation).
- 1
config.stretches = Rails.env.test? ? 1 : 10
# Setup a pepper to generate the encrypted password.
# config.pepper = '33c14b410d29eb88ee285a40d8e4ffa962c7155cbf46644825771a11207180d76dffa79671bea55138ed6f00414d51f2f590838fe38b1a4849ac93bf117fac20'
# ==> Configuration for :confirmable
# A period that the user is allowed to access the website even without
# confirming their account. For instance, if set to 2.days, the user will be
# able to access the website for two days without confirming their account,
# access will be blocked just in the third day. Default is 0.days, meaning
# the user cannot access the website without confirming their account.
# config.allow_unconfirmed_access_for = 2.days
# A period that the user is allowed to confirm their account before their
# token becomes invalid. For example, if set to 3.days, the user can confirm
# their account within 3 days after the mail was sent, but on the fourth day
# their account can't be confirmed with the token any more.
# Default is nil, meaning there is no restriction on how long a user can take
# before confirming their account.
# config.confirm_within = 3.days
# If true, requires any email changes to be confirmed (exactly the same way as
# initial account confirmation) to be applied. Requires additional unconfirmed_email
# db field (see migrations). Until confirmed, new email is stored in
# unconfirmed_email column, and copied to email column on successful confirmation.
- 1
config.reconfirmable = true
# Defines which key will be used when confirming an account
# config.confirmation_keys = [ :email ]
# ==> Configuration for :rememberable
# The time the user will be remembered without asking for credentials again.
# config.remember_for = 2.weeks
# If true, extends the user's remember period when remembered via cookie.
# config.extend_remember_period = false
# Options to be passed to the created cookie. For instance, you can set
# secure: true in order to force SSL only cookies.
# config.rememberable_options = {}
# ==> Configuration for :validatable
# Range for password length.
- 1
config.password_length = 8..128
# Email regex used to validate email formats. It simply asserts that
# one (and only one) @ exists in the given string. This is mainly
# to give user feedback and not to assert the e-mail validity.
# config.email_regexp = /\A[^@]+@[^@]+\z/
# ==> Configuration for :timeoutable
# The time you want to timeout the user session without activity. After this
# time the user will be asked for credentials again. Default is 30 minutes.
# config.timeout_in = 30.minutes
# If true, expires auth token on session timeout.
# config.expire_auth_token_on_timeout = false
# ==> Configuration for :lockable
# Defines which strategy will be used to lock an account.
# :failed_attempts = Locks an account after a number of failed attempts to sign in.
# :none = No lock strategy. You should handle locking by yourself.
# config.lock_strategy = :failed_attempts
# Defines which key will be used when locking and unlocking an account
# config.unlock_keys = [ :email ]
# Defines which strategy will be used to unlock an account.
# :email = Sends an unlock link to the user email
# :time = Re-enables login after a certain amount of time (see :unlock_in below)
# :both = Enables both strategies
# :none = No unlock strategy. You should handle unlocking by yourself.
# config.unlock_strategy = :both
# Number of authentication tries before locking an account if lock_strategy
# is failed attempts.
# config.maximum_attempts = 20
# Time interval to unlock the account if :time is enabled as unlock_strategy.
# config.unlock_in = 1.hour
# Warn on the last attempt before the account is locked.
# config.last_attempt_warning = false
# ==> Configuration for :recoverable
#
# Defines which key will be used when recovering the password for an account
# config.reset_password_keys = [ :email ]
# Time interval you can reset your password with a reset password key.
# Don't put a too small interval or your users won't have the time to
# change their passwords.
- 1
config.reset_password_within = 6.hours
# ==> Configuration for :encryptable
# Allow you to use another encryption algorithm besides bcrypt (default). You can use
# :sha1, :sha512 or encryptors from others authentication tools as :clearance_sha1,
# :authlogic_sha512 (then you should set stretches above to 20 for default behavior)
# and :restful_authentication_sha1 (then you should set stretches to 10, and copy
# REST_AUTH_SITE_KEY to pepper).
#
# Require the `devise-encryptable` gem when using anything other than bcrypt
# config.encryptor = :sha512
# ==> Scopes configuration
# Turn scoped views on. Before rendering "sessions/new", it will first check for
# "users/sessions/new". It's turned off by default because it's slower if you
# are using only default views.
# config.scoped_views = false
# Configure the default scope given to Warden. By default it's the first
# devise role declared in your routes (usually :user).
# config.default_scope = :user
# Set this configuration to false if you want /users/sign_out to sign out
# only the current scope. By default, Devise signs out all scopes.
# config.sign_out_all_scopes = true
# ==> Navigation configuration
# Lists the formats that should be treated as navigational. Formats like
# :html, should redirect to the sign in page when the user does not have
# access, but formats like :xml or :json, should return 401.
#
# If you have any extra navigational formats, like :iphone or :mobile, you
# should add them to the navigational formats lists.
#
# The "*/*" below is required to match Internet Explorer requests.
# config.navigational_formats = ['*/*', :html]
# The default HTTP method used to sign out a resource. Default is :delete.
- 1
config.sign_out_via = :delete
# ==> OmniAuth
# Add a new OmniAuth provider. Check the wiki for more information on setting
# up on your models and hooks.
# config.omniauth :github, 'APP_ID', 'APP_SECRET', scope: 'user,public_repo'
# ==> Warden configuration
# If you want to use other strategies, that are not supported by Devise, or
# change the failure app, you can configure them inside the config.warden block.
#
# config.warden do |manager|
# manager.intercept_401 = false
# manager.default_strategies(scope: :user).unshift :some_external_strategy
# end
# ==> Mountable engine configurations
# When using Devise inside an engine, let's call it `MyEngine`, and this engine
# is mountable, there are some extra configurations to be taken into account.
# The following options are available, assuming the engine is mounted as:
#
# mount MyEngine, at: '/my_engine'
#
# The router that invoked `devise_for`, in the example above, would be:
# config.router_name = :my_engine
#
# When using omniauth, Devise cannot automatically set Omniauth path,
# so you need to do it manually. For the users scope, it would be:
# config.omniauth_path_prefix = '/my_engine/users/auth'
end
# Be sure to restart your server when you modify this file.
# Configure sensitive parameters which will be filtered from the log file.
- 1
Rails.application.config.filter_parameters += [:password]
# Be sure to restart your server when you modify this file.
# Your secret key for verifying the integrity of signed cookies.
# If you change this key, all old signed cookies will become invalid!
# Make sure the secret is at least 30 characters and all random,
# no regular words or you'll be exposed to dictionary attacks.
# You can use `rake secret` to generate a secure secret key.
# Make sure your secret_key_base is kept private
# if you're sharing your code publicly.
# Although this is not needed for an api-only application, rails4
# requires secret_key_base or secret_token to be defined, otherwise an
# error is raised.
# Using secret_token for rails3 compatibility. Change to secret_key_base
# to avoid deprecation warning.
# Can be safely removed in a rails3 api-only application.
- 1
TennisNg::Application.config.secret_token = 'e84578a39b9aef2b2aac44a0bb1217b32094482f49d68c883cacccf6bf7c7b053ca13713addf57e1e8971dca1898f1907366498aa60d41cbd21c94a013dcc01b'
# Be sure to restart your server when you modify this file.
#
# This file contains settings for ActionController::ParamsWrapper
# Enable parameter wrapping for JSON.
# ActiveSupport.on_load(:action_controller) do
# wrap_parameters format: [:json] if respond_to?(:wrap_parameters)
# end
# To enable root element in JSON for ActiveRecord objects.
# ActiveSupport.on_load(:active_record) do
# self.include_root_in_json = true
# end
- 1
require 'api_constraints'
- 1
Rails.application.routes.draw do
- 1
scope '/api', constraints: { format: 'json' } do
- 1
scope module: :v1, constraints: ApiConstraints.new(version: 1, default: true) do
- 1
get 'user' => 'user#show'
- 1
resources :sessions, only: [:create, :destroy]
- 1
resources :players, except: [:new, :edit]
- 1
resources :teams, except: [:new, :edit]
- 1
resources :matches, except: [:new, :edit]
- 1
resources :match_scoreboard, only: [:show]
- 1
post 'match_scoreboard/:id' => 'match_scoreboard#update'
end
end
end
# Determine if a router request matches an api version number
- 1
class ApiConstraints
# * *Args* : options
# - +version+ -> number
# - Version number to match
# - +default+ -> Boolean
# - If request does not identify a version, default to +version+
- 1
def initialize(options)
- 6
@version = options[:version]
- 6
@default = options[:default]
end
# Determine whether a request matches
# an api version number
#
# * *Args* :
# - +request+ -> router request
# * *Returns* : Boolean
# - +:true+ - the request matches the api version
# - +:false+ - the request does not match the api version
- 1
def matches?(request)
- 5
accept = request.headers['Accept']
- 5
if accept.nil?
- 2
@default
else
- 3
if @default && !accept.include?(PREFIX)
- 1
true
else
- 2
accept.include?("#{PREFIX}#{@version}")
end
end
end
- 1
private
# version prefix string
- 1
PREFIX = 'application/tennis.scoreboard.v'
end
# Class to help "play" a match. This class is used to generate seed data and test data.
#
- 1
class MatchPlayer
# Class method to play a match.
# Calls MatchPlayer.play
# * *Args* :
# - +match+ -> Match
# - +scores+ -> Array
# - Array of characters within an array of sets
- 1
def self.play(match, scores)
- 83
MatchPlayer.new(match).play(scores)
end
# Convert an array of numeric scores into an array of
# character scores.
# This method is convenient for creating scores when the
# exact order of wins and losses is not important.
# Pass the result of this method to #play
#
# * *Args* :
# - +scores+ -> Array
# - Array of numeric pairs within an array of sets
# * *Returns* : Array
# - Array of characters within an array of sets
#
# === Scores Example
# [[6, 2],[3,2]]
#
# This array of numeric scores result in the following array of character scores:
# [['w', 'l', 'w', 'l', 'w', 'w', 'w', 'w'], ['w', 'l', 'w', 'l', 'w']]
- 1
def self.convert_scores(scores)
- 257
scores.map { |pair| convert_set_score(pair[0], pair[1]) }
end
# * *Args* :
# - +match+ -> Match
- 1
def initialize(match)
- 170
@match = match
end
# Play a match
# * *Args* :
# - +scores+ -> array of set scores
# - Array of characters within an array of sets
#
# === Scores Example
# [['l', 'w', 'w', 'w', 'w', 'w', 'w'], ['w', 'l', 'w', 'l', 'w']]
#
# This array indicates that the first opponent is to win the first set 6-1,
# and be ahead 3-2 in the second.
#
- 1
def play(scores)
- 83
match.play_match! :start_play
# apply_first_servers
- 253
scores.each { |v| play_set(v) }
end
# Start the match by executing the
# +:start_play+ action
- 1
def start_play
- 81
match.save!
- 81
if match.play_match? :start_play
- 81
match.play_match! :start_play
end
end
# Start the match and start the first game by executing the
# +:start_play+ and +:start_game+ actions
- 1
def start_first_game
- 20
start_play
- 20
if match.play_match?(:start_game) && no_games?
- 20
if match.doubles
- 9
match.play_match! :start_game, opponent: match.first_team.first_player
else
- 11
match.play_match! :start_game, opponent: match.first_player
end
end
end
# Start the next set by executing the
# +:start_set+ action
- 1
def start_set_game
# Starting set also starts first game
- 6
match.play_match! :start_set
end
# Indicate that the first opponent is to win
- 1
FIRST_OPPONENT_WIN = %w(w).freeze
# Indicate that the second opponent is to win
- 1
SECOND_OPPONENT_WIN = %w(l).freeze
- 1
private
- 1
attr_reader :match
# Play a set
- 1
def play_set(scores)
- 170
start_set
- 170
play_games(scores)
end
# Create an array of 'w' and 'l' that may be passed to #play_set
# Parameter values first_wins: 6, second_wins: 1 would return
# %w(w l w w w w w).
- 1
def self.convert_set_score(first_wins, second_wins)
- 172
score = []
# mix wins and losses so that set is not won prematurely
- 172
min = [first_wins, second_wins].min
- 546
1.upto(min) { score << FIRST_OPPONENT_WIN + SECOND_OPPONENT_WIN }
# now, add winning games
- 172
win = if first_wins > second_wins
- 85
FIRST_OPPONENT_WIN
else
- 87
SECOND_OPPONENT_WIN
end
- 705
1.upto((first_wins - second_wins).abs) { score << win }
- 172
score.flatten
end
- 1
def no_games?
- 20
match.match_sets.count == 1 && match.match_sets[0].set_games.count == 0
end
- 1
def start_set
- 170
if match.play_match? :start_match_tiebreak
- 31
match.play_match! :start_match_tiebreak
- 139
elsif match.play_match? :start_set
- 56
match.play_match! :start_set
end
end
- 1
def play_games(scores)
- 170
scores.each do |w_or_l|
- 1271
winner = if w_or_l == 'w'
- 647
match.first_team
else
- 624
match.second_team
end
- 1271
play_game(winner)
end
end
- 1
def play_game(winner)
- 1271
if match.play_match?(:start_game) || match.play_match?(:win_game)
- 1224
play_normal_game winner
- 47
elsif match.play_match? :win_match_tiebreak
- 19
match.play_match! :win_match_tiebreak, opponent: winner
- 28
elsif match.play_match?(:start_tiebreak) || match.play_match?(:win_tiebreak)
- 28
play_tiebreak winner
else
invalid_game
end
end
- 1
def play_tiebreak(winner)
- 28
if match.play_match? :start_tiebreak
- 28
match.play_match! :start_tiebreak
end
- 28
match.play_match! :win_tiebreak, opponent: winner
end
- 1
def play_normal_game(winner)
- 1224
if match.play_match? :start_game
# May need a first server
- 1168
param = nil
- 1168
if match.doubles
- 297
if match.first_player_server.nil?
- 17
param = match.first_team.first_player
elsif match.second_player_server.nil?
- 17
param = match.second_team.first_player
end
else
- 871
if match.first_player_server.nil?
- 66
param = match.first_player
end
end
- 1168
match.play_match! :start_game, opponent: param
end
- 1224
match.play_match! :win_game, opponent: winner
end
- 1
def invalid_game
raise "Invalid game. Set: #{match.last_set.ordinal},"\
"Game: #{match.last_set.last_game.ordinal}"
end
end
- 1
require 'rails_helper'
- 1
module ControllersShared
- 1
def self.message_text(id)
- 52
if id.is_a?(Symbol)
- 28
case id
when :cant_be_blank
- 12
'can\'t be blank'
when :already_taken
- 12
'has already been taken'
when :not_found
- 4
'not found'
else
id.to_s
end
else
- 24
id.to_s
end
end
- 1
def self.exclude_attribute(obj, exclude)
- 10
attributes = obj.clone
- 10
attributes.delete(exclude)
- 10
attributes
end
- 1
def self.change_attribute(obj, name, value)
- 8
attributes = obj.clone
- 8
attributes[name] = value
- 8
attributes
end
- 1
RSpec::Matchers.define :include_error do |message|
- 16
match do |json|
- 16
@text = ControllersShared::message_text(message)
- 16
json.key?(:errors) && json[:errors] == @text
end
- 16
failure_message do
"expect response to include error: #{@text}"
end
- 16
failure_message_when_negated do
"do not expect response to include error: #{@text}"
end
end
- 1
RSpec::Matchers.define :include_error_named do |name, message|
- 18
match do |json|
- 18
@text = ControllersShared::message_text(message)
- 18
json.key?(:errors) && json[:errors].key?(name) && json[:errors][name][0] == @text
end
- 18
description do
- 18
"include error :#{name} with value of \"#{ControllersShared::message_text(message)}\""
end
- 18
failure_message do
"expect response to include error: #{@text}"
end
- 18
failure_message_when_negated do
"do not expect response to include error: #{@text}"
end
end
- 1
RSpec::Matchers.define :have_errors do
- 8
match do |json|
- 8
json.key?(:errors) && !json[:errors].empty?
end
- 8
failure_message do
'expect to have errors'
end
- 8
failure_message_when_negated do
'do not expect to have errors'
end
end
- 1
RSpec.shared_examples 'a response with success code' do |code|
- 52
it { is_expected.to respond_with code }
end
- 1
RSpec.shared_examples 'a response with error code' do |code|
- 84
it { is_expected.to respond_with code }
end
- 1
RSpec.shared_examples 'login required' do
- 24
it { expect(json_response).to include_error 'Login required' }
- 12
it_behaves_like 'a response with error code', 403
end
- 1
RSpec.shared_examples 'not authorized' do
- 2
it { expect(json_response).to include_error 'Not authorized' }
- 1
it_behaves_like 'a response with error code', 401
end
- 1
RSpec.shared_examples 'not found' do
- 11
it_behaves_like 'a response with error code', 404
end
- 1
RSpec.shared_examples 'attribute error' do |name, message|
- 30
it { expect(json_response).to include_error_named name, message }
- 15
it_behaves_like 'a response with error code', 422
end
- 1
RSpec.shared_examples 'general error' do |message|
- 4
it { expect(json_response).to include_error message }
- 2
it_behaves_like 'a response with error code', 422
end
- 1
RSpec.shared_examples 'an error when delete referenced entity' do |message|
- 6
it { expect(json_response).to include_error_named :errors, message }
- 6
it { is_expected.to respond_with 422 }
end
- 1
RSpec.shared_examples 'player response' do
- 6
it { expect(json_response).to include :id, :name }
end
- 1
RSpec.shared_examples 'player list response' do
- 2
it { expect(json_response[0]).to include :id, :name }
end
- 1
RSpec.shared_examples 'team response' do
- 4
it { expect(json_response).to include :id, :name, :first_player, :second_player }
end
- 1
RSpec.shared_examples 'team list response' do
- 2
it { expect(json_response[0]).to include :id, :name, :first_player, :second_player }
end
- 1
RSpec.shared_examples 'doubles match response' do
- 8
it { expect(json_response).to include :id, :title, :first_team, :second_team, :doubles }
end
- 1
RSpec.shared_examples 'doubles teams' do
- 1
describe('first_team') do
- 3
subject { Team.find_by_id(json_response[:first_team][:id]) }
- 2
it { is_expected.not_to be_nil }
- 2
it { expect(subject.doubles).to be_truthy }
end
- 1
describe('second_team') do
- 3
subject { Team.find_by_id(json_response[:second_team][:id]) }
- 2
it { is_expected.not_to be_nil }
- 2
it { expect(subject.doubles).to be_truthy }
end
end
- 1
RSpec.shared_examples 'match list response' do
- 2
it { expect(json_response[0]).to include :id, :title, :doubles }
end
- 1
RSpec.shared_examples 'singles match response' do
- 8
it { expect(json_response).to include :id, :title, :first_player, :second_player, :doubles }
end
- 1
RSpec.shared_examples 'singles players' do
- 1
describe('first_player') do
- 2
subject { Player.find_by_id(json_response[:first_player][:id]) }
- 2
it { is_expected.not_to be_nil }
end
- 1
describe('second_player') do
- 2
subject { Player.find_by_id(json_response[:second_player][:id]) }
- 2
it { is_expected.not_to be_nil }
end
end
- 1
RSpec.shared_examples 'action response' do
- 7
it do
- 7
expect(json_response).to include :id, :title, :doubles,
:actions, :errors, :sets, :state, :winner, :servers
end
- 14
it { is_expected.to respond_with 200 }
end
- 1
RSpec.shared_examples 'doubles match scoreboard response' do
- 3
it_behaves_like 'action response'
- 6
it { expect(json_response).to include :first_team, :second_team }
end
- 1
RSpec.shared_examples 'singles match scoreboard response' do
- 4
it_behaves_like 'action response'
- 8
it { expect(json_response).to include :first_player, :second_player }
end
- 1
RSpec.shared_examples 'accepted action' do
- 10
it { expect(json_response).not_to have_errors }
end
- 1
RSpec.shared_examples 'denied action' do
- 6
it { expect(json_response).to have_errors }
end
end
- 1
RSpec.configure do |c|
- 1
c.extend ControllersShared, type: :controller
end
- 1
require 'rails_helper'
- 1
require 'controllers/controllers_shared'
- 1
RSpec.describe V1::ApplicationController, { type: :controller } do
- 4
let(:user1) { FactoryGirl.create :user }
- 1
controller do
- 1
def index
- 9
authorize_user!
end
end
- 1
def set_authorization(token)
- 6
request.headers['Authorization'] = token
end
- 1
describe 'authorize user' do
- 1
context 'when no auth token' do
- 4
before { get :index }
- 1
it 'should not have user' do
- 1
expect(subject.current_user).to be_nil
end
- 1
it_behaves_like 'login required'
end
- 1
context 'when invalid auth token' do
- 1
before do
- 3
set_authorization 'abcd'
- 3
get :index
end
- 1
it 'should not have user' do
- 1
expect(subject.current_user).to be_nil
end
- 1
it_behaves_like 'not authorized'
end
- 1
context 'when valid auth token' do
- 1
before do
- 3
set_authorization user1.auth_token
- 3
get :index
end
- 1
it 'should have user' do
- 1
expect(subject.current_user).not_to be_nil
end
- 2
it { is_expected.to be_user_signed_in }
- 1
it_behaves_like 'a response with success code', 200
end
end
end
- 1
require 'rails_helper'
- 1
require 'controllers/controllers_shared'
- 1
RSpec.describe V1::MatchScoreboardController, { type: :controller } do
- 29
let(:new_user) { FactoryGirl.create :user }
- 15
let(:doubles_match) { FactoryGirl.create :doubles_match }
- 6
let(:singles_match) { FactoryGirl.create :singles_match }
- 3
let(:not_found_match_id) { doubles_match.id + singles_match.id }
- 1
let(:match_title) { doubles_match.title + 'a' }
- 1
describe 'GET #show' do
- 1
context 'when doubles' do
- 4
before { get :show, id: doubles_match.id }
- 1
it_behaves_like 'doubles match scoreboard response'
end
- 1
context 'when singles' do
- 4
before { get :show, id: singles_match.id }
- 1
it_behaves_like 'singles match scoreboard response'
end
- 1
context 'when does not exist' do
- 2
before { get :show, id: not_found_match_id }
- 1
it_behaves_like 'not found'
end
end
- 1
describe 'PUT/PATCH #update' do
- 1
def post_action(id, action, param = nil)
- 30
params = { action: action }
- 30
params.merge!(param) if param
- 30
post :update, id: id, match_scoreboard: params
end
- 1
context 'when authorized' do
- 29
before { api_authorization_header new_user.auth_token }
- 1
context('when is started') do
- 5
before { post_action doubles_match.id, :start_play }
- 1
it_behaves_like 'doubles match scoreboard response'
- 1
it_behaves_like 'accepted action'
end
- 1
context 'when action is unknown' do
- 1
describe 'unknown name' do
- 3
before { post_action doubles_match.id, :xxx }
- 1
it_behaves_like 'general error', 'Unknown action: xxx'
end
- 1
describe 'requires a parameter' do
- 3
before { post_action doubles_match.id, :win_game }
- 1
it_behaves_like 'general error', "Unknown team for action: #{:win_game}"
end
end
- 1
context 'when is denied' do
- 2
before { post_action doubles_match.id, :start_tiebreak }
- 1
it_behaves_like 'denied action'
end
- 1
context 'when does not exists' do
- 2
before { post_action not_found_match_id, :start_play }
- 1
it_behaves_like 'not found'
end
- 1
context 'when pass parameter' do
- 1
describe 'to start game' do
- 5
let(:match) { FactoryGirl.build :play_singles_match, start_play: true }
- 1
before do
# identify first server
- 4
post_action match.id, :start_game, { player: match.first_player.id, version: match.play_version }
end
- 1
it_behaves_like 'singles match scoreboard response'
- 1
it_behaves_like 'accepted action'
end
- 1
describe 'to win' do
- 1
context 'doubles' do
- 5
let(:match) { FactoryGirl.build :play_doubles_match, start_first_game: true }
- 1
before do
# identify winning team
- 4
post_action match.id, :win_game, { team: match.first_team.id }
end
- 1
it_behaves_like 'doubles match scoreboard response'
- 1
it_behaves_like 'accepted action'
end
end
- 1
context 'singles' do
- 5
let(:match) { FactoryGirl.build :play_singles_match, start_first_game: true }
- 1
before do
# identify winning player
- 4
post_action match.id, :win_game, { player: match.first_player.id }
end
- 1
it_behaves_like 'singles match scoreboard response'
- 1
it_behaves_like 'accepted action'
end
end
- 1
context 'when pass scoreboard version' do
- 7
let(:match) { FactoryGirl.build :play_singles_match, start_play: true }
- 1
describe 'that is behind' do
- 1
before do
- 1
post_action match.id, :start_game, { player: match.first_player.id, version: match.play_version - 1 }
end
- 1
it_behaves_like 'denied action'
end
- 1
describe 'that is ahead' do
- 1
before do
- 1
post_action match.id, :start_game, { player: match.first_player.id, version: match.play_version + 1 }
end
- 1
it_behaves_like 'denied action'
end
- 1
describe 'that is correct' do
- 1
before do
- 4
post_action match.id, :start_game, { player: match.first_player.id, version: match.play_version }
end
- 1
it_behaves_like 'singles match scoreboard response'
- 1
it_behaves_like 'accepted action'
end
end
end
- 1
context('when not authorized') do
- 3
before { post_action 1, :start_play }
- 1
it_behaves_like 'login required'
end
end
end
- 1
require 'rails_helper'
- 1
require 'controllers/controllers_shared'
- 1
RSpec.describe V1::MatchesController, { type: :controller } do
- 38
let(:new_user) { FactoryGirl.create :user }
- 37
let(:doubles_match_title) { 'doubles match' }
- 22
let(:singles_match_title) { 'singles match' }
- 3
let(:doubles_match2_title) { 'doubles match 2' }
- 20
let(:doubles_match) { FactoryGirl.create :doubles_match, title: doubles_match_title }
- 20
let(:doubles_match_attributes) { FactoryGirl.attributes_for :doubles_match, title: doubles_match_title }
- 3
let(:doubles_match2) { FactoryGirl.create :doubles_match, title: doubles_match2_title }
- 15
let(:singles_match) { FactoryGirl.create :singles_match, title: singles_match_title }
- 4
let(:not_found_match_id) { doubles_match.id + singles_match.id }
- 5
let(:new_doubles_match_title) { 'new doubles' }
- 4
let(:new_singles_match_title) { 'new singles' }
- 8
let(:singles_match_attributes) { FactoryGirl.attributes_for :singles_match, :player_ids, title: singles_match_title }
- 1
let(:player1) { FactoryGirl.create :player, name: 'one' }
- 1
let(:player2) { FactoryGirl.create :player, name: 'two' }
- 1
def exclude_doubles_attribute(exclude)
- 6
ControllersShared::exclude_attribute(doubles_match_attributes, exclude)
end
- 1
def change_doubles_attribute(name, value)
- 2
ControllersShared::change_attribute(doubles_match_attributes, name, value)
end
- 1
describe 'GET #index' do
- 1
before do
- 2
doubles_match
- 2
get :index
end
- 1
it_behaves_like 'match list response'
- 1
it_behaves_like 'a response with success code', 200
end
- 1
describe 'GET #show' do
- 1
context 'when doubles exists' do
- 3
before { get :show, id: doubles_match.id }
- 1
it_behaves_like 'doubles match response'
- 1
it_behaves_like 'a response with success code', 200
end
- 1
context 'when singles exists' do
- 3
before { get :show, id: singles_match.id }
- 1
it_behaves_like 'singles match response'
- 1
it_behaves_like 'a response with success code', 200
end
- 1
context 'when does not exists' do
- 2
before { get :show, id: not_found_match_id }
- 1
it_behaves_like 'not found'
end
end
- 1
describe 'POST #create' do
- 1
context 'when authorized' do
- 17
before { api_authorization_header new_user.auth_token }
- 1
context 'when doubles is successfully created' do
- 4
before { post :create, { match: doubles_match_attributes } }
- 1
it 'should render title' do
- 1
expect(json_response[:title]).to eql doubles_match_attributes[:title]
end
- 1
it_behaves_like 'doubles match response'
- 1
it_behaves_like 'a response with success code', 201
end
- 1
context 'when singles is successfully created' do
- 4
before { post :create, { match: singles_match_attributes } }
- 1
it 'should render json representation' do
- 1
expect(json_response[:title]).to eql singles_match_attributes[:title]
end
- 1
it_behaves_like 'singles match response'
- 1
it_behaves_like 'a response with success code', 201
end
- 1
context 'when title is nil' do
- 1
before do
- 1
post :create, { match: change_doubles_attribute(:title, nil) }
end
- 1
it 'should render json representation' do
- 1
expect(json_response[:title]).to start_with('Match')
end
end
- 1
context 'when title is blank' do
- 1
before do
- 1
post :create, { match: change_doubles_attribute(:title, '') }
end
- 1
it 'should generate a title' do
- 1
expect(json_response[:title]).to start_with('Match')
end
end
- 1
context 'when first team is missing' do
- 1
before do
- 2
post :create, { match: exclude_doubles_attribute(:first_team_id) }
end
- 1
it_behaves_like 'attribute error', :first_team, :cant_be_blank
end
- 1
context 'when second team is missing' do
- 1
before do
- 2
post :create, { match: exclude_doubles_attribute(:second_team_id) }
end
- 1
it_behaves_like 'attribute error', :second_team, :cant_be_blank
end
- 1
context 'when scoring is missing' do
- 1
before do
- 2
post :create, { match: exclude_doubles_attribute(:scoring) }
end
- 1
it_behaves_like 'attribute error', :scoring, :cant_be_blank
end
- 1
context 'when title is already taken' do
- 1
before do
- 2
doubles_match
- 2
post :create, { match: doubles_match_attributes }
end
- 1
it_behaves_like 'attribute error', :title, :already_taken
end
end
- 1
context 'when not authorized' do
- 1
context 'when is not created' do
- 3
before { post :create, { match: {} } }
- 1
it_behaves_like 'login required'
end
end
end
- 1
describe 'PUT/PATCH #update' do
- 1
context 'when authorized' do
- 20
before { api_authorization_header new_user.auth_token }
- 1
context 'when is successfully updated' do
- 1
context 'singles' do
- 1
before do
- 3
patch :update, { id: singles_match.id, match: { title: new_singles_match_title } }
end
- 1
it 'should render json representation' do
- 1
expect(json_response[:title]).to eql new_singles_match_title
end
- 1
it_behaves_like 'singles match response'
- 1
it_behaves_like 'a response with success code', 200
end
- 1
context 'doubles' do
- 1
before do
- 3
patch :update, { id: doubles_match.id, match: { title: new_doubles_match_title } }
end
- 1
it 'should render json representation' do
- 1
expect(json_response[:title]).to eql new_doubles_match_title
end
- 1
it_behaves_like 'doubles match response'
- 1
it_behaves_like 'a response with success code', 200
end
end
- 1
context 'when change singles to doubles' do
- 1
before do
- 6
patch :update, { id: singles_match.id, match: doubles_match_attributes }
end
- 1
it_behaves_like 'doubles match response'
- 1
it_behaves_like 'doubles teams'
- 1
it_behaves_like 'a response with success code', 200
end
- 1
context 'when change doubles to singles' do
- 1
before do
- 4
patch :update, { id: doubles_match.id, match: singles_match_attributes }
end
- 1
it_behaves_like 'singles match response'
- 1
it_behaves_like 'singles players'
- 1
it_behaves_like 'a response with success code', 200
end
- 1
context 'when does not exists' do
- 1
before do
- 1
patch :update, { id: not_found_match_id, match: { title: new_doubles_match_title } }
end
- 1
it_behaves_like 'not found'
end
- 1
context 'when title is already taken' do
- 1
before do
- 2
patch :update, { id: doubles_match.id, match: { title: doubles_match2.title } }
end
- 1
it_behaves_like 'attribute error', :title, :already_taken
end
end
- 1
context 'when not authorized' do
- 3
before { patch :update, { id: 1, match: { title: 'abc' } } }
- 1
it_behaves_like 'login required'
end
end
- 1
describe 'DELETE #destroy' do
- 1
context 'when authorized' do
- 3
before { api_authorization_header new_user.auth_token }
- 1
context 'when exists' do
- 2
before { delete :destroy, id: doubles_match.id }
- 2
it { is_expected.to respond_with 204 }
end
- 1
context 'when does not exists' do
- 2
before { delete :destroy, id: not_found_match_id }
- 1
it_behaves_like 'not found'
end
end
- 1
context 'when not authorized' do
- 3
before { delete :destroy, id: 1 }
- 1
it_behaves_like 'login required'
end
end
end
- 1
require 'rails_helper'
- 1
require 'controllers/controllers_shared'
- 1
RSpec.describe V1::PlayersController, { type: :controller } do
- 22
let(:new_user) { FactoryGirl.create :user }
- 22
let(:player_name) { 'player1'}
- 3
let(:player2_name) { 'player2'}
- 19
let(:player) { FactoryGirl.create :player, name: player_name }
- 3
let(:player2) { FactoryGirl.create :player, name: player2_name }
- 6
let(:player_attributes) { FactoryGirl.attributes_for :player, name: player_name }
- 5
let(:new_player_name) { 'new player' }
- 4
let(:not_found_player_id) { player.id + 1 }
- 3
let(:doubles_team) { FactoryGirl.create :doubles_team }
- 3
let(:doubles_match) { FactoryGirl.create :doubles_match }
- 1
describe 'GET #index' do
- 1
context 'when exists' do
- 1
before do
- 2
player
- 2
get :index
end
- 1
it_behaves_like 'player list response'
- 1
it_behaves_like 'a response with success code', 200
end
end
- 1
describe 'GET #show' do
- 1
context 'when exists' do
- 1
before do
- 3
get :show, id: player.id
end
- 1
it_behaves_like 'player response'
- 1
it 'should render json representation' do
- 1
expect(json_response[:name]).to eql player.name
end
- 1
it_behaves_like 'a response with success code', 200
end
- 1
context 'when does not exists' do
- 1
before do
- 1
get :show, id: not_found_player_id
end
- 1
it_behaves_like 'not found'
end
end
- 1
describe 'POST #create' do
- 1
context 'when authorized' do
- 1
before do
- 7
api_authorization_header new_user.auth_token
end
- 1
context 'when is successfully created' do
- 1
before do
- 3
post :create, { player: player_attributes }
end
- 1
it_behaves_like 'player response'
- 1
it 'should render json representation' do
- 1
expect(json_response[:name]).to eql player_attributes[:name]
end
- 1
it_behaves_like 'a response with success code', 201
end
- 1
context 'when name is missing' do
- 1
before do
- 2
invalid_model_attributes = {}
- 2
post :create, { player: invalid_model_attributes }
end
- 1
it_behaves_like 'attribute error', :name, :cant_be_blank
end
- 1
context 'when name is already taken' do
- 1
before do
- 2
player
- 2
post :create, { player: player_attributes }
end
- 1
it_behaves_like 'attribute error', :name, :already_taken
end
end
- 1
context 'when not authorized' do
- 1
context 'when is not created' do
- 1
before do
- 2
post :create, { player: {} }
end
- 1
it_behaves_like 'login required'
end
end
end
- 1
describe 'PUT/PATCH #update' do
- 1
context 'when authorized' do
- 1
before do
- 8
api_authorization_header new_user.auth_token
end
- 1
context 'when is successfully updated' do
- 1
before do
- 3
patch :update, { id: player.id, player: { name: new_player_name } }
end
- 1
it_behaves_like 'player response'
- 1
it 'should render json representation' do
- 1
expect(json_response[:name]).to eql new_player_name
end
- 1
it_behaves_like 'a response with success code', 200
end
- 1
context 'when does not exists' do
- 1
before do
- 1
patch :update, { id: not_found_player_id, player: { name: new_player_name } }
end
- 1
it_behaves_like 'not found'
end
- 1
context 'when name is missing' do
- 1
before do
- 2
patch :update, { id: player.id, player: { name: '' } }
end
- 1
it_behaves_like 'attribute error', :name, :cant_be_blank
end
- 1
context 'when name is already taken' do
- 1
before do
- 2
patch :update, { id: player.id, player: { name: player2.name } }
end
- 1
it_behaves_like 'attribute error', :name, :already_taken
end
end
- 1
context 'when not authorized' do
- 1
before do
- 2
patch :update, { id: 1, player: { } }
end
- 1
it_behaves_like 'login required'
end
end
- 1
describe 'DELETE #destroy' do
- 1
context 'when authorized' do
- 1
before do
- 6
api_authorization_header new_user.auth_token
end
- 1
context 'when exists' do
- 1
before do
- 1
delete :destroy, id: player.id
end
- 1
it_behaves_like 'a response with success code', 204
end
- 1
context 'when does not exists' do
- 1
before do
- 1
delete :destroy, id: not_found_player_id
end
- 1
it_behaves_like 'not found'
end
- 1
context 'when player on team' do
- 1
before do
- 2
delete :destroy, id: doubles_team.first_player_id
end
- 1
it_behaves_like 'an error when delete referenced entity', 'Can\'t delete a player in a match or on a team'
end
- 1
context 'when player in match' do
- 1
before do
- 2
delete :destroy, id: doubles_match.first_team.first_player_id
end
- 1
it_behaves_like 'an error when delete referenced entity', 'Can\'t delete a player in a match or on a team'
end
end
- 1
context 'when not authorized' do
- 1
before do
- 2
delete :destroy, id: 1
end
- 1
it_behaves_like 'login required'
end
end
end
- 1
require 'rails_helper'
- 1
require 'controllers/controllers_shared'
- 1
RSpec.describe V1::SessionsController, { type: :controller } do
- 5
let(:user) { FactoryGirl.create :user }
- 2
let(:password) { '12345678' }
- 3
let(:unknown_password) { '99999999' }
- 1
describe 'POST #create' do
- 1
context 'when the credentials are correct' do
- 1
before do
- 1
credentials = { username: user.username, password: password }
- 1
post :create, { session: credentials }
end
- 1
it_behaves_like 'a response with success code', 200
end
- 1
context 'when the credentials are incorrect' do
- 1
before do
- 2
credentials = { username: user.username, password: unknown_password }
- 2
post :create, { session: credentials }
end
- 2
it { expect(json_response).to include_error 'Invalid username or password' }
- 1
it_behaves_like 'a response with error code', 422
end
end
- 1
describe 'DELETE #destroy' do
- 1
before do
# sign_in user
end
- 1
describe 'valid token' do
- 1
before do
- 1
delete :destroy, id: user.auth_token
end
- 1
it_behaves_like 'a response with success code', 204
end
- 1
describe 'invalid token' do
- 1
before do
- 1
delete :destroy, id: 'xxxxxxx'
end
# NoOp
- 1
it_behaves_like 'a response with success code', 204
end
end
end
- 1
require 'rails_helper'
- 1
require 'controllers/controllers_shared'
- 1
RSpec.describe V1::TeamsController, { type: :controller } do
- 26
let(:new_user) { FactoryGirl.create :user }
- 23
let(:doubles_team_name) {'doubles team'}
- 3
let(:doubles_team2_name) {'doubles team 2'}
- 23
let(:player_name) {'player name'}
- 3
let(:player2_name) {'player2 name'}
- 23
let(:doubles_team) { FactoryGirl.create :doubles_team, name: doubles_team_name,
first_player_name: player_name }
- 3
let(:doubles_team2) { FactoryGirl.create :doubles_team, name: doubles_team2_name,
first_player_name: player2_name }
- 17
let(:doubles_team_attributes) { FactoryGirl.attributes_for :doubles_team }
- 4
let(:not_found_team_id) {doubles_team.id + 1}
- 5
let(:not_found_player_id) {doubles_team.id}
- 3
let(:doubles_match) { FactoryGirl.create :doubles_match }
- 2
let(:team_name) { doubles_team.name + 'a'}
- 1
def exclude_team_attribute(exclude)
- 4
ControllersShared::exclude_attribute(doubles_team_attributes, exclude)
end
- 1
def change_team_attribute(name, value)
- 6
ControllersShared::change_attribute(doubles_team_attributes, name, value)
end
- 1
describe 'GET #index' do
- 1
before do
- 2
doubles_team
- 2
get :index
end
- 1
it_behaves_like 'team list response'
- 1
it_behaves_like 'a response with success code', 200
end
- 1
describe 'GET #show' do
- 1
context 'when exists' do
- 4
before { get :show, id: doubles_team.id }
- 1
it_behaves_like 'team response'
- 1
it 'should render the json representation' do
- 1
expect(json_response[:name]).to eql doubles_team.name
end
- 1
it_behaves_like 'a response with success code', 200
end
- 1
context 'when exists doubles team' do
- 4
before { get :show, id: doubles_team.id }
- 1
it_behaves_like 'team response'
- 1
it 'should render the json representation' do
- 1
expect(json_response[:name]).to eql doubles_team.name
end
- 1
it_behaves_like 'a response with success code', 200
end
- 1
context 'when does not exists' do
- 2
before { get :show, id: not_found_team_id }
- 1
it_behaves_like 'not found'
end
end
- 1
describe 'POST #create' do
- 1
context 'when authorized' do
- 17
before { api_authorization_header new_user.auth_token }
- 1
context 'when is successfully created' do
- 1
before do
- 2
post :create, { team: doubles_team_attributes }
end
- 1
it 'should render the json representation' do
- 1
expect(json_response[:name]).to eql doubles_team_attributes[:name]
end
- 1
it_behaves_like 'a response with success code', 201
end
- 1
context 'when doubles team is successfully created' do
- 3
before { post :create, { team: doubles_team_attributes } }
- 1
it 'should render the json representation' do
- 1
expect(json_response[:name]).to eql doubles_team_attributes[:name]
end
- 1
it_behaves_like 'a response with success code', 201
end
- 1
context 'when name is blank' do
- 1
before do
- 1
post :create, { team: change_team_attribute(:name, '') }
end
- 1
it 'should generate a name' do
- 1
expect(json_response[:name]).to start_with('Team')
end
end
- 1
context 'when name is nil' do
- 1
before do
- 1
post :create, { team: change_team_attribute(:name, nil) }
end
- 1
it 'should generate a name' do
- 1
expect(json_response[:name]).to start_with('Team')
end
end
- 1
context 'when first player is missing' do
- 1
before do
- 2
post :create, { team: exclude_team_attribute(:first_player_id) }
end
- 1
it_behaves_like 'attribute error', :first_player, :cant_be_blank
end
- 1
context 'when second player is missing' do
- 1
before do
- 2
post :create, { team: exclude_team_attribute(:second_player_id) }
end
- 1
it_behaves_like 'attribute error', :second_player, 'must be specified'
end
- 1
context 'when name is already taken' do
- 1
before do
- 2
doubles_team
- 2
post :create, { team: doubles_team_attributes }
end
- 1
it_behaves_like 'attribute error', :name, :already_taken
end
- 1
context 'when first player not found' do
- 1
before do
- 2
post :create, { team: change_team_attribute(:first_player_id, not_found_player_id) }
end
- 1
it_behaves_like 'attribute error', :first_player, :not_found
end
- 1
context 'when second player is not found' do
- 1
before do
- 2
post :create, { team: change_team_attribute(:second_player_id, not_found_player_id) }
end
- 1
it_behaves_like 'attribute error', :second_player, :not_found
end
end
- 1
context 'when not authorized' do
- 1
context 'when is not created' do
- 3
before { post :create, { team: {} } }
- 1
it_behaves_like 'login required'
end
end
end
- 1
describe 'PUT/PATCH #update' do
- 1
context 'when authorized' do
- 6
before { api_authorization_header new_user.auth_token }
- 1
context 'when is successfully updated' do
- 3
let(:team_name) { doubles_team.name + 'a'}
- 1
before do
- 2
patch :update, { id: doubles_team.id, team: { name: team_name } }
end
- 1
it 'should render the json representation' do
- 1
expect(json_response[:name]).to eql team_name
end
- 1
it_behaves_like 'a response with success code', 200
end
- 1
context 'when does not exists' do
- 1
before do
- 1
patch :update, { id: not_found_team_id, team: { name: team_name } }
end
- 1
it_behaves_like 'not found'
end
- 1
context 'when name is already taken' do
- 1
before do
- 2
patch :update, { id: doubles_team.id, team: { name: doubles_team2.name } }
end
- 1
it_behaves_like 'attribute error', :name, :already_taken
end
end
- 1
context 'when not authorized' do
- 3
before { patch :update, { id: 1, team: {} } }
- 1
it_behaves_like 'login required'
end
end
- 1
describe 'DELETE #destroy' do
- 1
context 'when authorized' do
- 5
before { api_authorization_header new_user.auth_token }
- 1
context 'when exists' do
- 2
before { delete :destroy, id: doubles_team.id }
- 1
it_behaves_like 'a response with success code', 204
end
- 1
context 'when does not exists' do
- 2
before { delete :destroy, id: not_found_team_id }
- 1
it_behaves_like 'not found'
end
- 1
context 'when team in match' do
- 3
before { delete :destroy, id: doubles_match.first_team_id }
- 1
it_behaves_like 'an error when delete referenced entity', 'Can\'t delete a team in a match'
end
end
- 1
context 'when not authorized' do
- 3
before { delete :destroy, id: 1 }
- 1
it_behaves_like 'login required'
end
end
end
- 1
require 'rails_helper'
- 1
require 'controllers/controllers_shared'
- 1
RSpec.describe V1::UserController, { type: :controller } do
- 3
let(:username1) { 'one' }
- 1
let(:username2) { 'two' }
- 1
let(:password) { '12345768' }
- 3
let(:user1) { FactoryGirl.create :user, username: username1 }
- 1
let(:user1_attributes) { FactoryGirl.attributes_for :user, username: username1 }
- 1
describe 'GET #show' do
- 1
context 'when is authorized' do
- 3
before { api_authorization_header user1.auth_token }
- 3
before { get :show }
- 1
it 'should render the json representation' do
- 1
expect(json_response[:username]).to eql user1.username
end
- 1
it_behaves_like 'a response with success code', 200
end
- 1
context 'when is not logged in' do
- 3
before { get :show }
- 1
it_behaves_like 'login required'
end
end
end
- 1
require 'match_player'
# Create matches in progress
- 1
FactoryGirl.define do
- 1
factory :play_singles_match, class: Match, parent: :singles_match do
- 1
transient do
- 1
scores []
- 1
start_play false
- 1
start_first_game false
- 1
start_set_game false
end
- 1
after(:build) do |subject, factory|
- 113
after_build(subject, factory)
end
end
- 1
factory :play_doubles_match, class: Match, parent: :doubles_match do
- 1
transient do
- 1
scores []
- 1
start_play false
- 1
start_first_game false
- 1
start_set_game false
end
- 1
after(:build) do |subject, factory|
- 46
after_build(subject, factory)
end
end
end
- 1
def after_build(subject, factory)
- 159
if factory.start_play
- 61
MatchPlayer.new(subject).start_play
end
- 159
if factory.start_first_game
- 20
MatchPlayer.new(subject).start_first_game
end
- 159
if factory.scores.count > 0
- 77
scores = MatchPlayer.convert_scores(factory.scores)
- 77
MatchPlayer.play(subject, scores)
end
- 159
if factory.start_set_game
- 6
MatchPlayer.new(subject).start_set_game
end
end
# == Schema Information
#
# Table name: match_sets
#
# id :integer not null, primary key
# match_id :integer not null
# ordinal :integer not null
# created_at :datetime not null
# updated_at :datetime not null
# scoring :string not null
# team_winner_id :integer
#
- 1
FactoryGirl.define do
- 1
factory :match_set, class: MatchSet do
- 1
transient do
- 1
match_title 'factory match'
end
- 1
ordinal 1
- 1
scoring 'six_game'
- 1
match_id do
- 17
(Match.find_by(title: match_title) || FactoryGirl.create(:singles_match, title: match_title)).id
end
end
end
# == Schema Information
#
# Table name: matches
#
# id :integer not null, primary key
# created_at :datetime not null
# updated_at :datetime not null
# first_team_id :integer not null
# second_team_id :integer not null
# scoring :string not null
# started :boolean default(FALSE), not null
# doubles :boolean default(FALSE), not null
# first_player_server_id :integer
# second_player_server_id :integer
# title :string
# team_winner_id :integer
# play_version :integer
#
- 1
FactoryGirl.define do
- 1
factory :doubles_match, class: Match do
- 1
transient do
- 1
first_player_name 'factory first player'
- 1
second_player_name 'factory second player'
- 1
third_player_name 'factory third player'
- 1
fourth_player_name 'factory fourth player'
- 1
first_team_name 'factory first team'
- 1
second_team_name 'factory second team'
end
- 1
title 'doubles match'
- 1
scoring :one_eight_game
- 1
doubles true
- 1
first_team_id do
(Team.find_by(name: first_team_name) || FactoryGirl.create(:doubles_team,
name: first_team_name,
first_player_name: first_player_name,
- 141
second_player_name: second_player_name)).id
end
- 1
second_team_id do
(Team.find_by(name: second_team_name) || FactoryGirl.create(:doubles_team,
name: second_team_name,
first_player_name: third_player_name,
- 141
second_player_name: fourth_player_name)).id
end
end
- 1
factory :singles_match, class: Match do
- 1
transient do
- 1
first_player_name 'first'
- 1
second_player_name 'second'
end
- 1
title 'singles match'
- 1
scoring :one_eight_game
- 1
doubles false
- 1
first_player do
- 197
MatchFactoryHelper::find_or_create_player(first_player_name)
end
- 1
second_player do
- 197
MatchFactoryHelper::find_or_create_player(second_player_name)
end
# used with FactoryGirl.attributes_for
- 1
trait :player_ids do
- 1
first_player_id do
- 7
p1 = MatchFactoryHelper::find_or_create_player(first_player_name)
- 7
p1.id
end
- 1
second_player_id do
- 7
p2 = MatchFactoryHelper::find_or_create_player(second_player_name)
- 7
p2.id
end
end
end
end
- 1
module MatchFactoryHelper
- 1
def self.find_or_create_player(player_name)
- 408
(Player.find_by(name: player_name) || FactoryGirl.create(:player, name: player_name))
end
end
# == Schema Information
#
# Table name: players
#
# id :integer not null, primary key
# name :string not null
# created_at :datetime not null
# updated_at :datetime not null
#
- 1
FactoryGirl.define do
- 1
factory :player do
- 1
name 'factory player'
end
end
# == Schema Information
#
# Table name: set_games
#
# id :integer not null, primary key
# ordinal :integer not null
# match_set_id :integer not null
# team_winner_id :integer
# created_at :datetime not null
# updated_at :datetime not null
# player_server_id :integer
# tiebreaker :boolean default(FALSE), not null
#
- 1
FactoryGirl.define do
- 1
factory :set_game, class: SetGame do
- 1
transient do
- 1
match_title 'factory match'
end
- 1
ordinal 1
- 1
match_set_id do
- 8
match = Match.find_by(title: match_title) || FactoryGirl.create(:singles_match, title: match_title)
- 8
set = match.match_sets.find_by(ordinal: 1) || FactoryGirl.create(:match_set, match_title: match_title)
- 8
set.id
end
end
end
# == Schema Information
#
# Table name: teams
#
# id :integer not null, primary key
# name :string
# first_player_id :integer not null
# second_player_id :integer
# created_at :datetime not null
# updated_at :datetime not null
# doubles :boolean default(FALSE), not null
#
- 1
FactoryGirl.define do
- 1
factory :doubles_team, class: Team do
- 1
transient do
- 1
first_player_name 'factory first player'
- 1
second_player_name 'factory second player'
end
- 1
name 'doubles team'
- 1
doubles true
- 1
first_player_id do
- 342
(Player.find_by(name: first_player_name) || FactoryGirl.create(:player, name: first_player_name)).id
end
- 1
second_player_id do
- 342
(Player.find_by(name: second_player_name) || FactoryGirl.create(:player, name: second_player_name)).id
end
end
end
# == Schema Information
#
# Table name: users
#
# id :integer not null, primary key
# username :string default(""), not null
# encrypted_password :string default(""), not null
# reset_password_token :string
# reset_password_sent_at :datetime
# remember_created_at :datetime
# sign_in_count :integer default(0), not null
# current_sign_in_at :datetime
# last_sign_in_at :datetime
# current_sign_in_ip :string
# last_sign_in_ip :string
# created_at :datetime
# updated_at :datetime
# auth_token :string default("")
#
- 1
FactoryGirl.define do
- 1
factory :user do
- 1
username 'factory user'
- 1
password '12345678'
- 1
password_confirmation '12345678'
end
end
- 1
require 'rails_helper'
- 1
require 'api_constraints'
- 1
RSpec.describe ApiConstraints, { type: :controller } do
- 1
context 'when no version and default' do
- 2
subject { ApiConstraints.new({ default: true }) }
- 1
it 'should match' do
- 1
expect(subject.matches?(request)).to be_truthy
end
end
- 1
context 'when no version and not default' do
- 2
subject { ApiConstraints.new({ default: false }) }
- 1
it 'should not match' do
- 1
expect(subject.matches?(request)).to be_falsy
end
end
- 1
context 'when not a version and default' do
- 1
before do
- 1
api_accept_header 'not.a.version'
end
- 2
subject { ApiConstraints.new({ default: true }) }
- 1
it 'should not match' do
- 1
expect(subject.matches?(request)).to be_truthy
end
end
- 1
context 'when correct version' do
- 1
before do
- 1
api_accept_header_version 1
end
- 2
subject { ApiConstraints.new({ version: 1, default: true }) }
- 1
it 'should match' do
- 1
expect(subject.matches?(request)).to be_truthy
end
end
- 1
context 'when incorrect version' do
- 1
before do
- 1
api_accept_header_version 2
end
- 2
subject { ApiConstraints.new({ version: 1, default: true }) }
- 1
it 'should not match' do
- 1
expect(subject.matches?(request)).to be_falsy
end
end
end
- 1
require 'rails_helper'
- 1
require 'match_player'
- 1
RSpec.describe MatchPlayer, { type: :model } do
- 1
describe '::convert_scores' do
- 2
subject { MatchPlayer.convert_scores([[1, 2]]) }
- 1
it 'should convert scores' do
- 1
expect(subject).to eq([%w(w l l)])
end
end
- 1
describe '::play_match' do
- 1
before do
- 6
MatchPlayer.play(match, scores)
end
- 7
subject { match }
- 1
context 'eight game pro set' do
- 3
let(:scores) { MatchPlayer.convert_scores([[0, 8]]) }
- 1
context 'singles' do
- 2
let(:match) { FactoryGirl.build(:singles_match, scoring: :one_eight_game) }
- 1
it 'should have winner' do
- 1
expect(subject.team_winner).to_not be_nil
end
end
- 1
context 'doubles' do
- 2
let(:match) { FactoryGirl.build(:doubles_match, scoring: :one_eight_game) }
- 1
it 'should have winner' do
- 1
expect(subject.team_winner).to_not be_nil
end
end
end
- 1
context 'two sets' do
- 3
let(:scores) { MatchPlayer.convert_scores([[0, 6], [6, 0], [1, 0]]) }
- 1
context 'singles' do
- 2
let(:match) { FactoryGirl.build(:singles_match, scoring: :two_six_game_ten_point) }
- 1
it 'should have winner' do
- 1
expect(subject.team_winner).to_not be_nil
end
end
- 1
context 'doubles' do
- 2
let(:match) { FactoryGirl.build(:doubles_match, scoring: :two_six_game_ten_point) }
- 1
it 'should have winner' do
- 1
expect(subject.team_winner).to_not be_nil
end
end
end
- 1
context 'three sets' do
- 3
let(:scores) { MatchPlayer.convert_scores([[0, 6], [6, 0], [6, 0]]) }
- 1
context 'singles' do
- 2
let(:match) { FactoryGirl.build(:singles_match, scoring: :three_six_game) }
- 1
it 'should have winner' do
- 1
expect(subject.team_winner).to_not be_nil
end
end
- 1
context 'doubles' do
- 2
let(:match) { FactoryGirl.build(:doubles_match, scoring: :three_six_game) }
- 1
it 'should have winner' do
- 1
expect(subject.team_winner).to_not be_nil
end
end
end
end
- 1
context 'invalid score' do
- 2
let(:scores) { MatchPlayer.convert_scores([[0, 7]]) }
- 2
let(:match) { FactoryGirl.build(:singles_match, scoring: :three_six_game) }
- 1
it 'should raise error' do
- 2
expect{MatchPlayer.play_match(match, scores)}.to raise_error StandardError
end
end
end
- 1
require 'rails_helper'
# Matches and shared examples for match_play specs
- 1
module MatchPlayShared
- 1
RSpec::Matchers.define :permit_action do |action|
- 8
match do |m|
- 8
m.play_match?(action)
end
- 8
description do
- 8
"permit #{action} action"
end
- 8
failure_message do
"expect to permit action #{action}"
end
- 8
failure_message_when_negated do
"expect to not permit action #{action}"
end
end
- 1
RSpec::Matchers.define :have_last_set_in_progress do
- 3
match do |m|
- 3
m.last_set.team_winner.nil? if m.last_set
end
- 3
failure_message do
'expect match to have set in progress'
end
- 3
failure_message_when_negated do
'do not expect match to have set in progress'
end
end
- 1
RSpec::Matchers.define :be_complete do
- 1
match do |m|
- 1
m.state == :complete
end
- 1
failure_message do
'expect to be complete'
end
- 1
failure_message_when_negated do
'do not expect to be complete'
end
end
- 1
RSpec::Matchers.define :permit_win_game do
- 3
match do |m|
- 3
m.play_match?(:win_game) || m.play_match?(:win_tiebreak)
end
- 3
description do
- 3
'permit win_game or win_tiebreak actions'
end
- 3
failure_message do
'expect to permit win_game or win_tiebreak actions'
end
- 3
failure_message_when_negated do
'do not expect to permit win_game or win_tiebreak actions'
end
end
- 1
RSpec::Matchers.define :have_game_count do |count|
- 4
match do |m|
- 4
m.match_sets.count == 1 && m.match_sets[0].set_games.count == count
end
- 4
description do
- 4
"have set with #{count} #{count > 1 ? 'games' : 'game'}"
end
- 4
failure_message do
"expect to have set with #{count} game(s)"
end
- 4
failure_message_when_negated do
"do not expect to set with have #{count} games(s)"
end
end
- 1
RSpec::Matchers.define :have_set_size do |size|
- 3
match do |m|
- 3
@actual = nil
- 3
sets = m.match_sets.reject(&:tiebreak?)
- 3
sets.each do |set|
- 6
unless size == set.win_threshold
@actual = set.win_threshold
end
end
- 3
sets.empty? || @actual ? false : true
end
- 3
description do
- 3
"have #{size} game set"
end
- 3
failure_message do
"expect to have #{size} game set, but have #{@actual} game set"
end
- 3
failure_message_when_negated do
"do not expect to have #{size} game set"
end
end
- 1
RSpec::Matchers.define :have_set_count do |count|
- 6
match do |m|
- 6
@actual = m.match_sets.reject(&:tiebreak?).count
- 6
@actual == count
end
- 6
description do
- 6
"have #{count} #{count > 1 ? 'sets' : 'set'}"
end
- 6
failure_message do
"expect to have #{count} set(s), but have #{@actual}"
end
- 6
failure_message_when_negated do
"do not expect to have #{count} set(s)"
end
end
- 1
RSpec::Matchers.define :have_match_tiebreak do
- 1
match do |m|
- 1
m.last_set.tiebreak?
end
- 1
failure_message do
'expect to have match tiebreak'
end
- 1
failure_message_when_negated do
'do not expect to have match tiebreak'
end
end
- 1
RSpec::Matchers.define :have_changes do
- 3
match do |m|
- 3
actions = [:discard_play, :remove_last_change]
- 3
actions.each do |action|
- 3
return false unless m.play_match? action
end
true
end
- 3
failure_message do
'expect to have changes'
end
- 3
failure_message_when_negated do
'do not expect to have changes'
end
end
- 1
RSpec::Matchers.define :have_complete_set do
- 2
match do |m|
- 2
last_set = m.last_set
- 2
last_set && last_set.state == :complete
end
- 2
failure_message do
'expect to have complete set'
end
- 2
failure_message_when_negated do
'do not expect to have complete set'
end
end
- 1
RSpec::Matchers.define :have_game_in_progress do
- 8
match do |m|
- 8
last_game = m.last_set.last_game if m.last_set
- 8
last_game.team_winner.nil? && last_game.player_server if last_game
end
- 8
failure_message do
'expect to have game in progress'
end
- 8
failure_message_when_negated do
'do not expect to have game in progress'
end
end
- 1
RSpec::Matchers.define :have_game_with_winner do
- 2
match do |m|
- 2
last_game = m.last_set.last_game if m.last_set
- 2
last_game.team_winner if last_game
end
- 2
failure_message do
'expect to have game with server'
end
- 2
failure_message_when_negated do
'do not expect to have game with server'
end
end
- 1
RSpec::Matchers.define :be_serving do |player_ordinal, doubles|
- 22
match do |m|
- 22
@expected = nil
- 22
@actual = nil
- 22
@last_game = m.last_set.last_game if m.last_set
- 22
if @last_game && @last_game.team_winner.nil?
- 22
players = [
m.doubles ? m.first_team.first_player : m.first_player,
m.doubles ? m.first_team.second_player : m.second_player,
m.doubles ? m.second_team.first_player : nil,
m.doubles ? m.second_team.second_player : nil
]
- 22
@expected = players[player_ordinal]
- 22
@actual = @last_game.player_server
- 22
@actual && @actual == @expected
else
false
end
end
- 22
description do
- 22
player = case player_ordinal
when 0
- 9
doubles ? 'first_team.first_player' : 'first_player'
when 1
- 8
doubles ? 'first_team.second_player' : 'second_player'
when 2
- 3
'second_team.first_player'
when 3
- 2
'second_team.second_player'
end
- 22
"#{player} be serving"
end
- 22
failure_message do
if @actual && @expected
"expected #{@expected.name} to be serving, but #{@actual.name} is serving"
elsif @last_game.nil?
'expected game in progress'
else
'expected server'
end
end
- 22
failure_message_when_negated do
if @actual
"Did not expect #{@actual.name} to be serving"
else
'Did not expect server'
end
end
end
- 1
RSpec.shared_examples 'a game not started' do
- 6
it { is_expected.not_to permit_win_game }
end
- 1
RSpec.shared_examples 'a match not started' do
- 6
it { is_expected.not_to have_changes }
end
- 1
RSpec.shared_examples 'a match just started' do
- 3
it_behaves_like 'a game not started'
- 3
it_behaves_like 'a match set in progress'
end
- 1
RSpec.shared_examples 'a match set in progress' do
- 6
it { is_expected.to have_last_set_in_progress }
end
- 1
RSpec.shared_examples 'a match with game in progress' do
- 16
it { is_expected.to have_game_in_progress }
end
- 1
RSpec.shared_examples 'a match with one game' do
- 6
it { is_expected.to have_game_count(1) }
end
- 1
RSpec.shared_examples 'a match with two games' do
- 2
it { is_expected.to have_game_count(2) }
end
- 1
RSpec.shared_examples 'a match with game won' do
- 4
it { is_expected.to have_game_with_winner }
end
- 1
RSpec.shared_examples 'a match can start set tiebreak' do
- 4
it { is_expected.to permit_action :start_tiebreak }
end
- 1
RSpec.shared_examples 'a match in set tiebreak' do
- 4
it { is_expected.to permit_action :win_tiebreak }
end
- 1
RSpec.shared_examples 'a match can start match tiebreak' do
- 4
it { is_expected.to permit_action :start_match_tiebreak }
end
- 1
RSpec.shared_examples 'a match in match tiebreak' do
- 4
it { is_expected.to permit_action :win_match_tiebreak }
end
- 1
RSpec.shared_examples 'a match with complete set' do
- 4
it { is_expected.to have_complete_set }
end
- 1
RSpec.shared_examples 'a match complete' do
- 2
it { is_expected.to be_complete }
end
- 1
RSpec.shared_examples 'a match with first game started' do
- 3
it_behaves_like 'a match with game in progress'
- 3
it_behaves_like 'a match with one game'
end
- 1
RSpec.shared_examples 'a match with second game started' do
- 1
it_behaves_like 'a match with game in progress'
- 1
it_behaves_like 'a match with two games'
end
- 1
RSpec.shared_examples 'a match with first player serving' do
- 18
it { is_expected.to be_serving(0, subject.doubles) }
end
- 1
RSpec.shared_examples 'a match with second player serving' do
- 16
it { is_expected.to be_serving(1, subject.doubles) }
end
- 1
RSpec.shared_examples 'a match with third player serving' do
- 6
it { is_expected.to be_serving(2, subject.doubles) }
end
- 1
RSpec.shared_examples 'a match with fourth player serving' do
- 4
it { is_expected.to be_serving(3, subject.doubles) }
end
- 1
RSpec.shared_examples 'a match with three sets' do
- 2
it { is_expected.to have_set_count(3) }
end
- 1
RSpec.shared_examples 'a match with two sets' do
- 4
it { is_expected.to have_set_count(2) }
end
- 1
RSpec.shared_examples 'a match with match tiebreak' do
- 2
it { is_expected.to have_match_tiebreak }
end
- 1
RSpec.shared_examples 'a match with one set' do
- 6
it { is_expected.to have_set_count(1) }
end
- 1
RSpec.shared_examples 'a match with six game sets' do
- 4
it { is_expected.to have_set_size(6) }
end
- 1
RSpec.shared_examples 'a match with eight game sets' do
- 2
it { is_expected.to have_set_size(8) }
end
- 1
def all_actions
[
- 1
:start_play,
:discard_play,
:start_set,
:start_game,
:start_tiebreak,
:remove_last_change,
:start_match_tiebreak,
:win_game,
:win_tiebreak,
:win_match_tiebreak
]
end
- 1
def first_actions
[
- 10
:start_play,
]
end
- 1
def win_actions
[
- 10
:win_game,
:win_tiebreak,
:win_match_tiebreak
]
end
end
- 1
RSpec.configure do |c|
- 1
c.extend MatchPlayShared, match_play_shared: true
end
- 1
require 'rails_helper'
- 1
require 'models/match_play_shared'
# Test start match, start set, win game, etc.
- 1
RSpec.describe 'Play', { type: :model, match_play_shared: true, play_actions: true } do
- 1
describe 'any match' do
- 10
subject { FactoryGirl.build(:singles_match) }
- 1
it_behaves_like 'a match not started'
- 1
it 'should detect unknown action' do
- 2
expect { subject.play_match? :xxx }.to raise_error Exceptions::UnknownOperation
end
- 1
it 'should respond with valid actions' do
- 1
expect(subject.valid_actions).to eq({ start_play: true })
end
- 1
context 'when attempt invalid actions' do
- 1
actions = all_actions.reject do |a|
- 10
first_actions.include?(a) or win_actions.include?(a)
end
- 1
actions.each do |action|
- 6
context "when #{action}" do
- 6
it 'should deny action' do
- 12
expect { subject.play_match! action }.to raise_error Exceptions::InvalidOperation
end
end
end
end
end
- 1
describe 'start match' do
- 9
subject { FactoryGirl.build(:play_singles_match, start_play: true) }
- 1
it_behaves_like 'a match just started'
- 1
context 'remove last change' do
- 2
before { subject.play_match! :remove_last_change }
- 1
it_behaves_like 'a match not started'
end
- 1
context 'discard play' do
- 2
before { subject.play_match! :discard_play }
- 1
it_behaves_like 'a match not started'
end
- 1
it 'should not start again' do
- 2
expect { subject.play_match! :start_play }.to raise_error Exceptions::InvalidOperation
end
- 1
context 'when win actions are denied' do
- 1
win_actions.each do |action|
- 3
context "when #{action}" do
- 3
it 'should deny action' do
- 6
expect { subject.play_match! action, opponent: subject.first_team }.to raise_error Exceptions::InvalidOperation
end
end
end
end
end
- 1
describe 'start game' do
- 1
context 'singles match' do
- 8
subject { FactoryGirl.build(:play_singles_match, start_play: true) }
- 1
context 'with first server' do
- 1
before do
- 4
subject.play_match! :start_game, opponent: subject.first_player
end
- 1
it_behaves_like 'a match with first game started'
- 1
context 'remove last change' do
- 3
before { subject.play_match! :remove_last_change }
- 1
it_behaves_like 'a match just started'
end
end
- 1
context 'with first server team' do
- 1
before do
- 2
subject.play_match! :start_game, opponent: subject.first_team
end
- 1
it_behaves_like 'a match with first game started'
end
- 1
context 'without server' do
- 1
it 'should not start' do
- 2
expect { subject.play_match! :start_game }.to raise_error Exceptions::InvalidOperation
end
end
end
- 1
context 'doubles match' do
- 12
subject { FactoryGirl.build(:play_doubles_match, start_play: true) }
- 1
context 'start first game with first server' do
- 1
before do
- 10
subject.play_match! :start_game, opponent: subject.first_team.first_player
end
- 1
it_behaves_like 'a match with first game started'
- 1
context 'remove last change' do
- 3
before { subject.play_match! :remove_last_change }
- 1
it_behaves_like 'a match just started'
end
- 1
context 'start second game' do
- 7
before { subject.play_match! :win_game, opponent: subject.first_team }
- 1
context 'start second game with second server' do
- 1
before do
- 3
subject.play_match! :start_game, opponent: subject.second_team.first_player
end
- 1
it_behaves_like 'a match with second game started'
- 1
context 'remove last change' do
- 1
before {
- 1
subject.play_match! :remove_last_change
- 1
subject.reload
}
- 1
it_behaves_like 'a match with game won'
end
end
- 1
context 'second game without server' do
- 1
it 'should not start' do
- 2
expect { subject.play_match! :start_game }.to raise_error Exceptions::InvalidOperation
end
end
- 1
context 'second game with invalid server' do
- 1
it 'should not allow same server' do
- 2
expect { subject.play_match! :start_game, opponent: subject.first_team.first_player }.to raise_error ActiveRecord::RecordInvalid
end
- 1
it 'should not allow server from same team' do
- 2
expect { subject.play_match! :start_game, opponent: subject.first_team.second_player }.to raise_error ActiveRecord::RecordInvalid
end
end
end
end
- 1
context 'first game without server' do
- 1
it 'should not start' do
- 2
expect { subject.play_match! :start_game }.to raise_error Exceptions::InvalidOperation
end
end
end
end
- 1
describe 'win game' do
- 5
subject { FactoryGirl.build(:play_singles_match, start_first_game: true) }
- 1
it_behaves_like 'a match with game in progress'
- 1
context 'with player' do
- 3
before { subject.play_match! :win_game, opponent: subject.first_team.first_player }
- 1
it_behaves_like 'a match with game won'
- 1
context 'remove last change' do
- 2
before { subject.play_match! :remove_last_change }
- 1
it_behaves_like 'a match with game in progress'
end
end
- 1
context 'without player' do
- 1
it 'should not win' do
- 2
expect { subject.play_match! :win_game }.to raise_error Exceptions::UnknownOperation
end
end
end
- 1
describe 'complete set' do
- 1
context 'single set match' do
- 1
subject do
- 2
FactoryGirl.build(:play_singles_match,
scoring: :two_six_game_ten_point, scores: [[6, 0], [0, 6]])
end
- 1
it_behaves_like 'a match with complete set'
- 1
context 'remove last change' do
- 1
before {
- 1
subject.play_match! :remove_last_change
}
- 1
it_behaves_like 'a match with game in progress'
end
end
end
- 1
describe 'set tiebreak' do
- 1
subject do
- 7
FactoryGirl.build(:play_singles_match, scoring: :one_eight_game,
scores: [[8, 8]])
end
- 1
it_behaves_like 'a match can start set tiebreak'
- 1
context 'start tiebreak' do
- 7
before { subject.play_match! :start_tiebreak }
- 1
it_behaves_like 'a match in set tiebreak'
- 1
context 'win tiebreak with player' do
- 4
before { subject.play_match! :win_tiebreak, opponent: subject.first_team }
- 1
it_behaves_like 'a match with complete set'
- 1
context 'remove last change' do
- 2
before { subject.play_match! :remove_last_change }
- 1
it_behaves_like 'a match in set tiebreak'
end
- 1
it 'should not win tiebreak again' do
- 1
expect {
- 1
subject.play_match! :win_tiebreak, opponent: subject.first_team
}.to raise_error Exceptions::InvalidOperation
end
end
- 1
context 'win tiebreak without player' do
- 1
it 'should not win' do
- 2
expect { subject.play_match! :win_tiebreak }.to raise_error Exceptions::UnknownOperation
end
end
- 1
context 'remove last change' do
- 1
before do
- 1
subject.play_match! :remove_last_change
- 1
subject.reload
end
- 1
it_behaves_like 'a match can start set tiebreak'
end
end
end
- 1
describe 'remove second set' do
- 1
subject do
- 3
FactoryGirl.build(:play_singles_match,
scoring: :two_six_game_ten_point, scores: [[6, 0]])
end
- 1
it_behaves_like 'a match with one set'
- 1
context 'start second set' do
- 3
before { subject.play_match! :start_set }
- 1
it_behaves_like 'a match with two sets'
- 1
context 'remove started set' do
- 1
before do
- 1
subject.play_match! :remove_last_change
- 1
subject.reload
end
- 1
it_behaves_like 'a match with one set'
end
end
end
- 1
describe 'match tiebreak' do
- 1
subject do
- 7
FactoryGirl.build(:play_singles_match,
scoring: :two_six_game_ten_point, scores: [[6, 0], [0, 6]])
end
- 1
it_behaves_like 'a match can start match tiebreak'
- 1
context 'start ' do
- 7
before { subject.play_match! :start_match_tiebreak }
- 1
it_behaves_like 'a match in match tiebreak'
- 1
context 'win with player' do
- 1
before do
- 3
subject.play_match! :win_match_tiebreak, opponent: subject.first_team
end
- 1
it_behaves_like 'a match complete'
- 1
context 'remove last change' do
- 2
before { subject.play_match! :remove_last_change }
- 1
it_behaves_like 'a match in match tiebreak'
end
- 1
it 'should not win again' do
- 1
expect {
- 1
subject.play_match! :win_match_tiebreak, opponent: subject.first_team
}.to raise_error Exceptions::InvalidOperation
end
end
- 1
context 'win without player' do
- 1
it 'should not win' do
- 2
expect { subject.play_match! :win_match_tiebreak }.to raise_error Exceptions::UnknownOperation
end
end
- 1
context 'remove last change' do
- 1
before do
- 1
subject.play_match! :remove_last_change
- 1
subject.reload # clears destroyed entities
end
- 1
it_behaves_like 'a match can start match tiebreak'
end
end
end
- 1
describe 'players serving first set' do
- 1
context 'singles' do
- 1
context 'alternate order' do # needed for complete code coverage
- 3
let(:winner) { subject.first_player.singles_team }
- 1
subject do
- 3
FactoryGirl.build(:play_singles_match, scoring: :three_six_game, start_play: true)
end
- 1
context 'first' do
- 1
before do
- 3
subject.play_match! :start_game, opponent: subject.second_player
end
- 1
it_behaves_like 'a match with second player serving'
- 1
context 'second' do
- 1
before do
- 2
subject.play_match! :win_game, opponent: winner
- 2
subject.play_match! :start_game
end
- 1
it_behaves_like 'a match with first player serving'
- 1
context 'third' do
- 1
before do
- 1
subject.play_match! :win_game, opponent: winner
- 1
subject.play_match! :start_game
end
- 1
it_behaves_like 'a match with second player serving'
end
end
end
end
- 1
context 'sequential order' do
- 3
let(:winner) { subject.first_player.singles_team }
- 1
subject do
- 3
FactoryGirl.build(:play_singles_match, scoring: :three_six_game, start_first_game: true)
end
- 1
context 'first' do
- 1
it_behaves_like 'a match with first player serving'
end
- 1
context 'second' do
- 1
before do
- 2
subject.play_match! :win_game, opponent: winner
- 2
subject.play_match! :start_game
end
- 1
it_behaves_like 'a match with second player serving'
- 1
context 'third' do
- 1
before do
- 1
subject.play_match! :win_game, opponent: winner
- 1
subject.play_match! :start_game
end
- 1
it_behaves_like 'a match with first player serving'
end
end
end
end
- 1
context 'doubles' do
- 1
context 'alternate order' do # needed for complete code coverage
- 5
let(:winner) { subject.first_team }
- 1
subject do
- 5
FactoryGirl.build(:play_doubles_match, scoring: :three_six_game, start_play: true)
end
- 1
context 'first' do
- 1
before do
- 5
subject.play_match! :start_game, opponent: subject.first_team.second_player
end
- 1
it_behaves_like 'a match with second player serving'
- 1
context 'second' do
- 1
before do
- 4
subject.play_match! :win_game, opponent: winner
- 4
subject.play_match! :start_game, opponent: subject.second_team.first_player
end
- 1
it_behaves_like 'a match with third player serving'
- 1
context 'third' do
- 1
before do
- 3
subject.play_match! :win_game, opponent: winner
- 3
subject.play_match! :start_game
end
- 1
it_behaves_like 'a match with first player serving'
- 1
context 'fourth' do
- 1
before do
- 2
subject.play_match! :win_game, opponent: winner
- 2
subject.play_match! :start_game
end
- 1
it_behaves_like 'a match with fourth player serving'
- 1
context 'fifth' do
- 1
before do
- 1
subject.play_match! :win_game, opponent: winner
- 1
subject.play_match! :start_game
end
- 1
it_behaves_like 'a match with second player serving'
end
end
end
end
end
end
- 1
context 'sequential order' do
- 5
let(:winner) { subject.first_team }
- 1
subject do
- 5
FactoryGirl.build(:play_doubles_match, scoring: :three_six_game, start_first_game: true)
end
- 1
context 'first' do
- 1
it_behaves_like 'a match with first player serving'
end
- 1
context 'second' do
- 1
before do
- 4
subject.play_match! :win_game, opponent: winner
- 4
subject.play_match! :start_game, opponent: subject.second_team.first_player
end
- 1
it_behaves_like 'a match with third player serving'
- 1
context 'third' do
- 1
before do
- 3
subject.play_match! :win_game, opponent: winner
- 3
subject.play_match! :start_game
end
- 1
it_behaves_like 'a match with second player serving'
- 1
context 'fourth' do
- 1
before {
- 2
subject.play_match! :win_game, opponent: winner
- 2
subject.play_match! :start_game
}
- 1
it_behaves_like 'a match with fourth player serving'
- 1
context 'fifth' do
- 1
before do
- 1
subject.play_match! :win_game, opponent: winner
- 1
subject.play_match! :start_game
end
- 1
it_behaves_like 'a match with first player serving'
end
end
end
end
end
end
end
- 1
describe 'players serving second set' do
- 1
context 'odd number of games' do
- 1
context 'singles' do
- 1
subject do
- 1
FactoryGirl.build(:play_singles_match, scoring: :three_six_game,
scores: [[6, 3]], start_set_game: true)
end
- 1
it_behaves_like 'a match with second player serving'
end
- 1
context 'doubles' do
- 1
subject do
- 1
FactoryGirl.build(:play_doubles_match, scoring: :three_six_game,
scores: [[6, 3]], start_set_game: true)
end
- 1
it_behaves_like 'a match with third player serving'
end
end
- 1
context 'even number of games' do
- 1
context 'singles' do
- 1
subject do
- 1
FactoryGirl.build(:play_singles_match, scoring: :three_six_game,
scores: [[7, 5]], start_set_game: true)
end
- 1
it_behaves_like 'a match with first player serving'
end
- 1
context 'doubles' do
- 1
subject do
- 1
FactoryGirl.build(:play_doubles_match, scoring: :three_six_game,
scores: [[6, 4]], start_set_game: true)
end
- 1
it_behaves_like 'a match with second player serving'
end
end
- 1
context 'tiebreak' do
- 1
context 'singles' do
- 1
subject do
- 1
FactoryGirl.build(:play_singles_match, scoring: :three_six_game,
scores: [[6, 7]], start_set_game: true)
end
- 1
it_behaves_like 'a match with first player serving'
end
- 1
context 'doubles' do
- 1
subject do
- 1
FactoryGirl.build(:play_doubles_match, scoring: :three_six_game,
scores: [[7, 6]], start_set_game: true)
end
- 1
it_behaves_like 'a match with first player serving'
end
end
end
- 1
describe 'match scoring' do
- 1
describe :one_eight_game do
- 1
subject do
- 2
FactoryGirl.build(:play_singles_match, scoring: :one_eight_game,
scores: [[8, 9]])
end
- 1
it_behaves_like 'a match with one set'
- 1
it_behaves_like 'a match with eight game sets'
end
- 1
describe :two_six_game_ten_point do
- 1
subject do
- 3
FactoryGirl.build(:play_singles_match, scoring: :two_six_game_ten_point,
scores: [[6, 3], [3, 6], [0, 1]])
end
- 1
it_behaves_like 'a match with two sets'
- 1
it_behaves_like 'a match with match tiebreak'
- 1
it_behaves_like 'a match with six game sets'
end
- 1
describe :three_six_game do
- 1
subject do
- 2
FactoryGirl.build(:play_singles_match, scoring: :three_six_game,
scores: [[6, 3], [3, 6], [7, 6]])
end
- 1
it_behaves_like 'a match with three sets'
- 1
it_behaves_like 'a match with six game sets'
end
end
- 1
describe 'match version' do
- 1
context 'initial value' do
- 2
subject { FactoryGirl.create(:play_singles_match) }
- 1
it 'should have nil version initially' do
- 1
expect(subject.play_version).to be_nil
end
end
- 1
context 'after match started' do
- 5
subject { FactoryGirl.build(:play_singles_match, start_play: true) }
- 1
it 'should have number' do
- 1
expect(subject.play_version).not_to be_nil
end
- 1
context 'when use valid version' do
- 2
before { subject.play_match! :start_game, { opponent: subject.first_player, version: subject.play_version } }
- 1
it_behaves_like 'a match with game in progress'
end
- 1
it 'should raise exception when version is behind' do
- 1
expect do
- 1
subject.play_match! :start_game, { opponent: subject.first_player, version: subject.play_version - 1 }
end.to raise_error Exceptions::InvalidOperation
end
- 1
it 'should raise exception when version is ahead' do
- 1
expect do
- 1
subject.play_match! :start_game, { opponent: subject.first_player, version: subject.play_version + 1 }
end.to raise_error Exceptions::InvalidOperation
end
end
end
- 1
describe '#compute_team_winner' do
- 1
context 'match complete' do
- 1
subject do
- 1
FactoryGirl.build(:play_singles_match, scoring: :one_eight_game,
scores: [[8, 0]])
end
- 1
it 'should have winner' do
- 1
expect(subject.compute_team_winner).to eql subject.team_winner
end
end
end
- 1
describe 'near winners' do
- 1
context 'singles' do
- 1
context 'one more set' do
- 1
context 'first player' do
- 1
subject do
- 2
FactoryGirl.build(:play_singles_match,
scoring: :two_six_game_ten_point, scores: [[6, 0]])
end
- 1
it 'should have first player near winner' do
- 1
expect(subject.near_team_winner? subject.first_team).to be_truthy
end
- 1
it 'should not have second player near winner' do
- 1
expect(subject.near_team_winner? subject.second_team).not_to be_truthy
end
end
- 1
context 'second player' do
- 1
subject do
- 2
FactoryGirl.build(:play_singles_match,
scoring: :two_six_game_ten_point, scores: [[0, 6]])
end
- 1
it 'should not have first player near winner' do
- 1
expect(subject.near_team_winner? subject.first_team).not_to be_truthy
end
- 1
it 'should have second player near winner' do
- 1
expect(subject.near_team_winner? subject.second_team).to be_truthy
end
end
end
- 1
context 'one more game' do
- 1
context 'first player' do
- 1
subject do
- 2
FactoryGirl.build(:play_singles_match,
scoring: :two_six_game_ten_point, scores: [[6, 0], [5, 0]])
end
- 1
it 'should have first player near winner' do
- 1
expect(subject.last_set.near_team_winner? subject.first_team).to be_truthy
end
- 1
it 'should not have second player near winner' do
- 1
expect(subject.last_set.near_team_winner? subject.second_team).not_to be_truthy
end
end
- 1
context 'second player' do
- 1
subject do
- 2
FactoryGirl.build(:play_singles_match,
scoring: :two_six_game_ten_point, scores: [[0, 6], [0, 5]])
end
- 1
it 'should not have first player near winner' do
- 1
expect(subject.last_set.near_team_winner? subject.first_team).not_to be_truthy
end
- 1
it 'should have second player near winner' do
- 1
expect(subject.last_set.near_team_winner? subject.second_team).to be_truthy
end
end
end
- 1
context 'set tiebreak' do
- 1
subject do
- 2
FactoryGirl.build(:play_singles_match,
scoring: :two_six_game_ten_point, scores: [[6, 6]])
end
- 1
it 'should have first player near tiebreak winner' do
- 1
expect(subject.last_set.near_team_winner? subject.first_team).to be_truthy
end
- 1
it 'should have second player near tiebreak winner' do
- 1
expect(subject.last_set.near_team_winner? subject.second_team).to be_truthy
end
end
- 1
context 'match tiebreak' do
- 1
subject do
- 4
FactoryGirl.build(:play_singles_match,
scoring: :two_six_game_ten_point, scores: [[6, 0], [0, 6], [0, 0]])
end
- 1
it 'should have first player near tiebreak winner' do
- 1
expect(subject.last_set.near_team_winner? subject.first_team).to be_truthy
end
- 1
it 'should have second player near tiebreak winner' do
- 1
expect(subject.last_set.near_team_winner? subject.second_team).to be_truthy
end
- 1
it 'should have first player near match winner' do
- 1
expect(subject.near_team_winner? subject.first_team).to be_truthy
end
- 1
it 'should have second player near match winner' do
- 1
expect(subject.near_team_winner? subject.second_team).to be_truthy
end
end
end
- 1
context 'doubles' do
- 1
context 'one more set' do
- 1
context 'first team needs one more set' do
- 1
subject do
- 2
FactoryGirl.build(:play_singles_match,
scoring: :two_six_game_ten_point, scores: [[6, 0]])
end
- 1
it 'should have first player near winner' do
- 1
expect(subject.near_team_winner? subject.first_team).to be_truthy
end
- 1
it 'should not have second player near winner' do
- 1
expect(subject.near_team_winner? subject.second_team).not_to be_truthy
end
end
- 1
context 'second player needs one more set' do
- 1
subject do
- 2
FactoryGirl.build(:play_singles_match,
scoring: :two_six_game_ten_point, scores: [[0, 6]])
end
- 1
it 'should not have first player near winner' do
- 1
expect(subject.near_team_winner? subject.first_team).not_to be_truthy
end
- 1
it 'should have second player near winner' do
- 1
expect(subject.near_team_winner? subject.second_team).to be_truthy
end
end
end
- 1
context 'one more game' do
- 1
context 'first team needs one more game' do
- 1
subject do
- 2
FactoryGirl.build(:play_singles_match,
scoring: :two_six_game_ten_point, scores: [[6, 0], [5, 0]])
end
- 1
it 'should have first team near winner' do
- 1
expect(subject.last_set.near_team_winner? subject.first_team).to be_truthy
end
- 1
it 'should not have second team near winner' do
- 1
expect(subject.last_set.near_team_winner? subject.second_team).not_to be_truthy
end
end
- 1
context 'second team needs one more game' do
- 1
subject do
- 2
FactoryGirl.build(:play_singles_match,
scoring: :two_six_game_ten_point, scores: [[0, 6], [0, 5]])
end
- 1
it 'should not have first team near winner' do
- 1
expect(subject.last_set.near_team_winner? subject.first_team).not_to be_truthy
end
- 1
it 'should have second team near winner' do
- 1
expect(subject.last_set.near_team_winner? subject.second_team).to be_truthy
end
end
end
end
end
end
# == Schema Information
#
# Table name: match_sets
#
# id :integer not null, primary key
# match_id :integer not null
# ordinal :integer not null
# created_at :datetime not null
# updated_at :datetime not null
# scoring :string not null
# team_winner_id :integer
#
- 1
require 'rails_helper'
- 1
RSpec.describe MatchSet, { type: :model } do
- 10
subject { FactoryGirl.build(:match_set) }
- 2
it { is_expected.to respond_to(:ordinal) }
- 2
it { is_expected.to respond_to(:scoring) }
- 2
it { is_expected.to validate_presence_of(:ordinal) }
- 2
it { is_expected.to validate_presence_of(:scoring) }
- 2
it { is_expected.to validate_presence_of(:match) }
- 2
it { is_expected.to respond_to(:match) }
- 1
it 'should be valid initially' do
- 1
is_expected.to be_valid
end
- 1
it 'should validate scoring value' do
- 1
subject.scoring = 'abc'
- 1
is_expected.to_not be_valid
end
- 1
describe '#destroy!' do
- 1
before do
- 1
subject.save!
end
- 1
it 'should remove set' do
- 4
expect { subject.destroy! }.to change { MatchSet.count }.by(-1)
end
end
end
# == Schema Information
#
# Table name: matches
#
# id :integer not null, primary key
# created_at :datetime not null
# updated_at :datetime not null
# first_team_id :integer not null
# second_team_id :integer not null
# scoring :string not null
# started :boolean default(FALSE), not null
# doubles :boolean default(FALSE), not null
# first_player_server_id :integer
# second_player_server_id :integer
# title :string
# team_winner_id :integer
# play_version :integer
#
- 1
require 'rails_helper'
- 1
RSpec.describe Match, { type: :model } do
- 5
let(:player_name) {'player'}
- 2
let(:player2_name) {'player2'}
- 3
let(:team_name) {'team'}
- 2
let(:team2_name) {'team2'}
- 3
let(:new_player) { FactoryGirl.create(:player, name: player_name) }
- 3
let(:new_team) { FactoryGirl.create(:doubles_team, name: team_name, first_player_name: player_name) }
- 2
let(:new_team_with_same_player) { FactoryGirl.create(:doubles_team, name: team2_name, first_player_name: player2_name) }
- 1
shared_examples 'match' do
- 4
it { is_expected.to respond_to(:title) }
- 4
it { is_expected.to respond_to(:doubles) }
- 4
it { is_expected.to respond_to(:team_winner) }
- 4
it { is_expected.to respond_to(:scoring) }
- 4
it { is_expected.to validate_presence_of(:scoring) }
- 4
it { is_expected.to respond_to(:first_team) }
- 4
it { is_expected.to respond_to(:second_team) }
- 4
it { is_expected.to respond_to(:first_player) }
- 4
it { is_expected.to respond_to(:second_player) }
- 4
it { is_expected.to validate_uniqueness_of(:title).ignoring_case_sensitivity }
- 2
it 'should be valid initially' do
- 2
is_expected.to be_valid
end
- 2
it 'should validate scoring value' do
- 2
subject.scoring = 'abc'
- 2
is_expected.to_not be_valid
end
- 2
describe '#destroy!' do
- 4
before { subject.save! }
- 2
it 'should remove match' do
- 8
expect { subject.destroy! }.to change { Match.count }.by(-1)
end
end
- 2
it 'should generate a default title' do
- 2
subject.title = nil
- 2
subject.save!
- 2
expect(subject.title).to start_with('Match')
end
end
- 1
context 'doubles match' do
- 22
subject { FactoryGirl.build(:doubles_match) }
- 1
it_behaves_like 'match'
- 2
it { is_expected.to validate_presence_of(:first_team) }
- 2
it { is_expected.to validate_presence_of(:second_team) }
- 1
it 'should validate existence of #first_team' do
- 1
subject.first_team_id = 0
- 1
is_expected.not_to be_valid
end
- 1
it 'should validate existence of #second_team' do
- 1
subject.second_team_id = 0
- 1
is_expected.not_to be_valid
end
- 1
it 'should validate that teams can\'t be the same' do
- 1
subject.first_team = subject.second_team
- 1
is_expected.not_to be_valid
end
- 1
it 'should validate that servers can\'t be the same' do
- 1
subject.first_player_server = subject.first_team.first_player
- 1
subject.second_player_server = subject.first_player_server
- 1
is_expected.not_to be_valid
end
- 1
it 'should validate that there are four different players' do
- 1
subject.second_team = new_team_with_same_player
- 1
is_expected.not_to be_valid
end
end
- 1
context 'singles match' do
- 20
subject { FactoryGirl.build(:singles_match) }
- 1
it_behaves_like 'match'
- 2
it { is_expected.to validate_presence_of(:first_player) }
- 2
it { is_expected.to validate_presence_of(:second_player) }
- 1
it 'should validate that #first_team must exist' do
- 1
subject.first_team_id = 0
- 1
is_expected.not_to be_valid
end
- 1
it 'should validate that #second team must exist' do
- 1
subject.second_team_id = 0
- 1
is_expected.not_to be_valid
end
- 1
it 'should validate that players can\'t be the same' do
- 1
subject.first_player = subject.second_player
- 1
is_expected.not_to be_valid
end
end
- 1
context 'doubles to singles' do
- 2
subject { FactoryGirl.build(:doubles_match) }
- 1
it 'validates that players are missing' do
- 1
subject.doubles = false
- 1
is_expected.not_to be_valid
end
end
- 1
context 'singles to doubles' do
- 2
subject { FactoryGirl.build(:singles_match) }
- 1
it 'validates that teams are missing' do
- 1
subject.doubles = true
- 1
is_expected.not_to be_valid
end
end
- 1
context 'match in progress' do
- 1
context 'singles' do
- 7
subject { FactoryGirl.build(:play_singles_match, start_play: true) }
- 1
it 'should validate cannot change #scoring' do
- 1
subject.scoring = :three_six_game
- 1
is_expected.to_not be_valid
end
- 1
it 'should validate cannot change #doubles' do
- 1
subject.doubles = true
- 1
is_expected.to_not be_valid
end
- 1
it 'should validate cannot change #second_player' do
- 1
subject.second_player = new_player
- 1
is_expected.to_not be_valid
end
- 1
it 'should validate cannot change #first_player' do
- 1
subject.first_player = new_player
- 1
is_expected.to_not be_valid
end
- 1
it 'should validate can change server' do
- 1
subject.first_player_server = subject.second_player
- 1
is_expected.to be_valid
end
- 1
context 'start game' do
- 1
before do
- 1
subject.play_match! :start_game, opponent: subject.first_player
- 1
subject.play_match! :win_game, opponent: subject.first_player.singles_team
end
- 1
it 'should validate cannot change server' do
- 1
subject.first_player_server = subject.second_player
- 1
is_expected.not_to be_valid
end
end
end
- 1
context 'doubles' do
- 8
subject { FactoryGirl.build(:play_doubles_match, start_play: true) }
- 1
it 'should validate cannot change #first_team' do
- 1
subject.first_team_id = new_team.id
- 1
is_expected.to_not be_valid
end
- 1
it 'should validate cannot change #second_team' do
- 1
subject.second_team = new_team
- 1
is_expected.to_not be_valid
end
- 1
it 'should validate cannot change #scoring' do
- 1
subject.scoring = :three_six_game
- 1
is_expected.to_not be_valid
end
- 1
it 'should validate can change first server' do
- 1
subject.first_player_server = subject.first_team.first_player
- 1
is_expected.to be_valid
end
- 1
context 'win first game' do
- 1
before do
- 3
subject.play_match! :start_game, opponent: subject.first_team.first_player
- 3
subject.play_match! :win_game, opponent: subject.first_team
end
- 1
it 'should validate cannot change first server' do
- 1
subject.first_player_server = subject.first_team.second_player
- 1
is_expected.not_to be_valid
end
- 1
it 'should validate can change second server' do
- 1
subject.second_player_server = subject.second_team.second_player
- 1
is_expected.to be_valid
end
- 1
context 'start second game' do
- 1
before do
- 1
subject.play_match! :start_game, opponent: subject.second_team.first_player
- 1
subject.play_match! :win_game, opponent: subject.first_team
end
- 1
it 'should validate cannot change second server' do
- 1
subject.second_player_server = subject.second_team.second_player
- 1
is_expected.not_to be_valid
end
end
end
end
end
end
# == Schema Information
#
# Table name: players
#
# id :integer not null, primary key
# name :string not null
# created_at :datetime not null
# updated_at :datetime not null
#
- 1
require 'rails_helper'
- 1
RSpec.describe Player, { type: :model } do
- 9
subject { FactoryGirl.build :player }
- 2
it { is_expected.to respond_to(:name) }
- 2
it { is_expected.to validate_presence_of(:name) }
- 2
it { is_expected.to validate_uniqueness_of(:name).ignoring_case_sensitivity }
- 1
describe '#singles_team' do
- 3
before { subject.singles_team! }
- 1
it 'should create singles team' do
- 1
expect(subject.singles_team).not_to be_nil
end
- 1
it 'should destroy singles team' do
- 4
expect { subject.destroy! }.to change { Team.count }.by(-1)
end
end
- 1
describe '#destroy!' do
- 4
before { subject.save! }
- 1
it 'should remove player' do
- 4
expect { subject.destroy! }.to change { Player.count }.by(-1)
end
- 1
context 'when referenced by team' do
- 1
before do
- 1
@team = FactoryGirl.build(:doubles_team, first_player_name: subject.name)
- 1
@team.save!
end
- 1
it 'should not destroy player' do
- 2
expect { subject.destroy! }.to raise_error(ActiveRecord::RecordNotDestroyed)
end
end
- 1
context 'when referenced by match' do
- 1
before do
- 1
@match = FactoryGirl.build(:singles_match, first_player_name: subject.name)
- 1
@match.save!
end
- 1
it 'should not destroy player' do
- 2
expect { subject.destroy! }.to raise_error(ActiveRecord::RecordNotDestroyed)
end
end
end
end
# == Schema Information
#
# Table name: set_games
#
# id :integer not null, primary key
# ordinal :integer not null
# match_set_id :integer not null
# team_winner_id :integer
# created_at :datetime not null
# updated_at :datetime not null
# player_server_id :integer
# tiebreaker :boolean default(FALSE), not null
#
- 1
require 'rails_helper'
- 1
RSpec.describe SetGame, { type: :model } do
- 9
subject { FactoryGirl.build(:set_game) }
- 2
it { is_expected.to respond_to(:ordinal) }
- 2
it { is_expected.to respond_to(:match_set) }
- 2
it { is_expected.to respond_to(:team_winner) }
- 2
it { is_expected.to respond_to(:tiebreaker) }
- 2
it { is_expected.to respond_to(:player_server) }
- 2
it { is_expected.to validate_presence_of(:ordinal) }
- 2
it { is_expected.to validate_presence_of(:match_set) }
- 1
describe '#destroy!' do
- 2
before { subject.save! }
- 1
it 'should remove set' do
- 4
expect { subject.destroy! }.to change { SetGame.count }.by(-1)
end
end
end
# == Schema Information
#
# Table name: teams
#
# id :integer not null, primary key
# name :string
# first_player_id :integer not null
# second_player_id :integer
# created_at :datetime not null
# updated_at :datetime not null
# doubles :boolean default(FALSE), not null
#
- 1
require 'rails_helper'
- 1
RSpec.describe Team, { type: :model } do
- 20
subject { FactoryGirl.build(:doubles_team) }
- 2
it { is_expected.to respond_to(:name) }
- 2
it { is_expected.to respond_to(:first_player) }
- 2
it { is_expected.to respond_to(:second_player) }
- 2
it { is_expected.to respond_to(:doubles) }
- 2
it { is_expected.to validate_uniqueness_of(:name).ignoring_case_sensitivity }
- 2
it { is_expected.to validate_presence_of(:first_player) }
- 1
it 'should be valid initially' do
- 1
is_expected.to be_valid
end
- 1
it 'should validate presence of #second_player' do
- 1
subject.second_player = nil
- 1
is_expected.to_not be_valid
end
- 1
it 'should validate existence of #first_player' do
- 1
subject.first_player_id = 0
- 1
is_expected.not_to be_valid
end
- 1
it 'should validate existence of #second_player' do
- 1
subject.second_player_id = 0
- 1
is_expected.not_to be_valid
end
- 1
it 'should validate to have two different players' do
- 1
subject.second_player = subject.first_player
- 1
is_expected.to_not be_valid
end
- 1
context 'when already have a team with players' do
- 1
it 'should validate duplicate players in same order' do
- 1
FactoryGirl.build(:doubles_team, name: 'other team').save!
- 1
is_expected.to_not be_valid
end
- 1
it 'should validate duplicate players in reverse order' do
FactoryGirl.build(:doubles_team, first_player_name: subject.second_player.name,
- 1
second_player_name: subject.first_player.name).save!
- 1
is_expected.to_not be_valid
end
end
- 1
context 'when in a match' do
- 5
let(:player_name) { 'all new player' }
- 5
let(:new_player) { FactoryGirl.create(:player, name: player_name) }
- 5
let(:match) { FactoryGirl.build(:doubles_match, first_team_name: subject.name) }
- 1
before do
- 4
subject.save!
- 4
match.save!
end
- 1
context 'when started' do
- 1
before do
- 2
match.play_match! :start_play
end
- 1
it 'should validate cannot change #second_player' do
- 1
subject.second_player = new_player
- 1
is_expected.to_not be_valid
end
- 1
it 'should validate cannot change #first_player' do
- 1
subject.first_player = new_player
- 1
is_expected.to_not be_valid
end
end
- 1
context 'when not started' do
- 1
it 'should validate can change #second_player' do
- 1
subject.second_player = new_player
- 1
is_expected.to be_valid
end
- 1
it 'should validate can change #first_player' do
- 1
subject.first_player = new_player
- 1
is_expected.to be_valid
end
end
end
- 1
describe '#destroy!' do
- 3
before { subject.save! }
- 1
it 'should remove team' do
- 4
expect { subject.destroy! }.to change { Team.count }.by(-1)
end
- 1
context 'when referenced by match' do
- 1
before do
- 1
FactoryGirl.build(:doubles_match, first_team_name: subject.name).save!
end
- 1
it 'should not remove team' do
- 2
expect { subject.destroy! }.to raise_error(ActiveRecord::RecordNotDestroyed)
end
end
end
end
# == Schema Information
#
# Table name: users
#
# id :integer not null, primary key
# username :string default(""), not null
# encrypted_password :string default(""), not null
# reset_password_token :string
# reset_password_sent_at :datetime
# remember_created_at :datetime
# sign_in_count :integer default(0), not null
# current_sign_in_at :datetime
# last_sign_in_at :datetime
# current_sign_in_ip :string
# last_sign_in_ip :string
# created_at :datetime
# updated_at :datetime
# auth_token :string default("")
#
- 1
require 'rails_helper'
- 1
RSpec.describe User, { type: :model } do
- 10
subject { FactoryGirl.build(:user) }
- 2
it { is_expected.to respond_to(:username) }
- 2
it { is_expected.to respond_to(:password) }
- 2
it { is_expected.to respond_to(:password_confirmation) }
- 2
it { is_expected.to respond_to(:auth_token) }
- 2
it { is_expected.to validate_presence_of(:username) }
- 2
it { is_expected.to validate_uniqueness_of(:username).ignoring_case_sensitivity }
- 2
it { is_expected.to validate_uniqueness_of(:auth_token).ignoring_case_sensitivity }
- 1
describe '#generate_authentication_token!' do
- 1
ATOKEN = 'auniquetoken'
- 1
it 'should generate a unique token' do
- 1
allow(Devise).to receive(:friendly_token).and_return(ATOKEN)
- 1
subject.generate_authentication_token!
- 1
expect(subject.auth_token).to eql ATOKEN
end
- 1
it 'should generate another token when duplicate' do
- 1
existing_user = FactoryGirl.create(:user, auth_token: ATOKEN)
- 1
subject.generate_authentication_token!
- 1
expect(subject.auth_token).not_to eql existing_user.auth_token
end
end
end
# This file is copied to spec/ when you run 'rails generate rspec:install'
- 1
ENV['RAILS_ENV'] ||= 'test'
- 1
require File.expand_path('../../config/environment', __FILE__)
# Prevent database truncation if the environment is production
- 1
abort("The Rails environment is running in production mode!") if Rails.env.production?
- 1
require 'spec_helper'
- 1
require 'rspec/rails'
# Add additional requires below this line. Rails is not loaded until this point!
# Requires supporting ruby files with custom matchers and macros, etc, in
# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
# run as spec files by default. This means that files in spec/support that end
# in _spec.rb will both be required and run as specs, causing the specs to be
# run twice. It is recommended that you do not name files matching this glob to
# end with _spec.rb. You can configure this pattern with the --pattern
# option on the command line or in ~/.rspec, .rspec or `.rspec-local`.
#
# The following line is provided for convenience purposes. It has the downside
# of increasing the boot-up time by auto-requiring all files in the support
# directory. Alternatively, in the individual `*_spec.rb` files, manually
# require only the support files necessary.
#
- 2
Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
# Checks for pending migration and applies them before tests are run.
# If you are not using ActiveRecord, you can remove this line.
- 1
ActiveRecord::Migration.maintain_test_schema!
- 1
RSpec.configure do |config|
# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
- 1
config.fixture_path = "#{::Rails.root}/spec/fixtures"
# If you're not using ActiveRecord, or you'd prefer not to run each of your
# examples within a transaction, remove the following line or assign false
# instead of true.
- 1
config.use_transactional_fixtures = true
# RSpec Rails can automatically mix in different behaviours to your tests
# based on their file location, for example enabling you to call `get` and
# `post` in specs under `spec/controllers`.
#
# You can disable this behaviour by removing the line below, and instead
# explicitly tag your specs with their type, e.g.:
#
# RSpec.describe UsersController, :type => :controller do
# # ...
# end
#
# The different available types are documented in the features, such as in
# https://relishapp.com/rspec/rspec-rails/docs
- 1
config.infer_spec_type_from_file_location!
# Filter lines from Rails gems in backtraces.
- 1
config.filter_rails_from_backtrace!
# arbitrary gems may also be filtered via:
# config.filter_gems_from_backtrace("gem name")
# This will pick up all of the fixtures defined in spec/fixtures into your
# database and you’ll be able to test with some sample data
# (eg. Countries, States, etc.)
- 1
config.global_fixtures = :all
#Including to test requests
- 1
config.include Request::JsonHelpers, type: :controller
- 1
config.include Request::HeadersHelpers, type: :controller
- 1
config.include Devise::TestHelpers, type: :controller
- 1
config.raise_errors_for_deprecations!
- 1
config.before(:each, type: :controller) do
- 183
include_default_accept_headers
end
end
- 1
Shoulda::Matchers.configure do |config|
- 1
config.integrate do |with|
# Choose a test framework:
- 1
with.test_framework :rspec
- 1
with.library :rails
end
end
- 1
require 'rails_helper'
- 1
require 'serializers/v1/match_serializer_shared'
- 1
RSpec.describe V1::MatchScoreboardSerializer, { type: :serializer, match_serializer_shared: true } do
- 1
def scoreboard_json(match)
- 24
serializer = V1::MatchScoreboardSerializer.new(match)
- 24
JSON.parse(serializer.to_json, symbolize_names: true)
end
- 1
describe 'empty match' do
- 1
context 'doubles' do
- 5
let(:match) { FactoryGirl.create :doubles_match }
- 1
subject do
- 4
scoreboard_json(match)
end
- 1
it_behaves_like 'a doubles scoreboard'
- 2
it { is_expected.to eql_match(match) }
end
- 1
context 'singles' do
- 5
let(:match) { FactoryGirl.create :singles_match }
- 1
subject do
- 4
scoreboard_json(match)
end
- 1
it_behaves_like 'a singles scoreboard'
- 2
it { is_expected.to eql_match(match) }
end
end
- 1
describe 'complete match' do
- 1
context 'doubles' do
- 5
let(:match) { FactoryGirl.create :play_doubles_match,
scoring: :two_six_game_ten_point,
scores: [[7, 6],[4, 6],[1, 0]]}
- 1
subject do
- 4
scoreboard_json(match)
end
- 1
it_behaves_like 'a doubles scoreboard'
- 2
it { is_expected.to eql_match(match) }
end
- 1
context 'singles' do
- 5
let(:match) { FactoryGirl.create :play_singles_match,
scoring: :two_six_game_ten_point,
scores: [[7, 6], [4, 6], [1, 0]]}
- 1
subject do
- 4
scoreboard_json(match)
end
- 1
it_behaves_like 'a singles scoreboard'
- 2
it { is_expected.to eql_match(match) }
end
end
- 1
describe 'near winners' do
- 1
context 'doubles' do
- 5
let(:match) { FactoryGirl.create :play_doubles_match,
scoring: :two_six_game_ten_point,
scores: [[7, 6],[4, 6], [0, 0]]}
- 1
subject do
- 4
scoreboard_json(match)
end
- 1
it_behaves_like 'a doubles scoreboard'
- 2
it { is_expected.to eql_match(match) }
end
- 1
context 'singles' do
- 5
let(:match) { FactoryGirl.create :play_singles_match,
scoring: :two_six_game_ten_point,
scores: [[7, 6], [4, 6], [0, 0]]}
- 1
subject do
- 4
scoreboard_json(match)
end
- 1
it_behaves_like 'a singles scoreboard'
- 2
it { is_expected.to eql_match(match) }
end
end
end
- 1
require 'rails_helper'
# Shared examples for testing match serialization
- 1
module V1::MatchSerializerShared
- 1
RSpec.shared_examples 'a match' do
- 20
it { is_expected.to include :id, :title, :doubles, :state, :scoring, :winner }
end
- 1
RSpec.shared_examples 'a doubles match' do
- 5
it_behaves_like 'a match'
- 5
it 'should be doubles' do
- 5
expect(subject[:doubles]).to eql(true)
end
end
- 1
RSpec.shared_examples 'a singles match' do
- 5
it_behaves_like 'a match'
- 5
it 'should be singles' do
- 5
expect(subject[:doubles]).to eql(false)
end
end
- 1
RSpec.shared_examples 'a scoreboard' do
- 12
it { is_expected.to include :sets, :actions, :errors, :servers, :version, :near_winners }
end
- 1
RSpec.shared_examples 'a doubles scoreboard' do
- 3
it_behaves_like 'a doubles match'
- 3
it_behaves_like 'a scoreboard'
end
- 1
RSpec.shared_examples 'a singles scoreboard' do
- 3
it_behaves_like 'a singles match'
- 3
it_behaves_like 'a scoreboard'
end
- 1
RSpec::Matchers.define :include do |member|
- 48
match do |json|
- 48
json.include? member
end
- 48
failure_message do
"expect to include #{member}"
end
- 48
failure_message_when_negated do
"do not expect to include #{member}"
end
end
- 1
RSpec::Matchers.define :eql_match do |match|
- 10
match do |json|
- 10
@helper = MatchEql.new(json, match)
- 10
h = @helper
- 10
h.eql_member :id
- 10
h.eql_member :doubles
- 10
h.eql_member_to_s :scoring
- 10
h.eql_member_to_s :state
- 10
h.eql_member_to_s :title
- 10
if match.doubles
- 5
h.eql_team :first_team
- 5
h.eql_team :second_team
- 5
h.record_result :winner, json[:winner].nil? || json[:winner] == match.team_winner.id
else
- 5
h.eql_team :first_player
- 5
h.eql_team :second_player
end
- 10
!h.failures?
end
- 10
description do
- 10
'json response have values from match'
end
- 10
failure_message do
@helper.failure_message
end
- 10
failure_message_when_negated do
'do not expect to match json'
end
end
- 1
class MatchEql
- 1
def initialize(json, match)
- 10
@not_equal = []
- 10
@json = json
- 10
@match = match
end
- 1
attr_reader :not_equal
- 1
attr_reader :json
- 1
attr_reader :match
- 1
def eql_member(member)
- 20
record_result member, json[member] == match.send(member)
end
- 1
def eql_member_to_s(member)
- 30
record_result member, json[member] == match.send(member).to_s
end
- 1
def eql_team(opponent)
- 20
record_result "#{opponent}.id", json[opponent][:id] == match.send(opponent).id
- 20
record_result "#{opponent}.name", json[opponent][:name] == match.send(opponent).name
end
- 1
def record_result(member, result)
- 95
not_equal.push member unless result
end
- 1
def failure_message
"expect members to match: #{not_equal.to_s}"
end
- 1
def failures?
- 10
!not_equal.empty?
end
end
end
- 1
RSpec.configure do |c|
- 1
c.extend V1::MatchSerializerShared, match_serializer_shared: true
end
- 1
require 'rails_helper'
- 1
require 'serializers/v1/match_serializer_shared'
- 1
RSpec.describe V1::MatchSerializer, { type: :serializer, match_serializer_shared: true } do
- 1
def match_json(match)
- 12
serializer = V1::MatchSerializer.new(match)
- 12
JSON.parse(serializer.to_json, symbolize_names: true)
end
- 1
describe 'empty match' do
- 1
context 'doubles' do
- 4
let(:match) { FactoryGirl.create :doubles_match }
- 1
subject do
- 3
match_json(match)
end
- 1
it_behaves_like 'a doubles match'
- 2
it { is_expected.to eql_match(match) }
end
- 1
context 'singles' do
- 4
let(:match) { FactoryGirl.create :singles_match }
- 1
subject do
- 3
match_json(match)
end
- 1
it_behaves_like 'a singles match'
- 2
it { is_expected.to eql_match(match) }
end
end
- 1
describe 'complete match' do
- 1
context 'doubles' do
- 4
let(:match) { FactoryGirl.create :play_doubles_match,
scoring: :two_six_game_ten_point,
scores: [[7, 6],[4, 6],[1, 0]]}
- 1
subject do
- 3
match_json(match)
end
- 1
it_behaves_like 'a doubles match'
- 2
it { is_expected.to eql_match(match) }
end
- 1
context 'singles' do
- 4
let(:match) { FactoryGirl.create :play_singles_match,
scoring: :two_six_game_ten_point,
scores: [[7, 6], [4, 6], [1, 0]]}
- 1
subject do
- 3
match_json(match)
end
- 1
it_behaves_like 'a singles match'
- 2
it { is_expected.to eql_match(match) }
end
end
end
- 1
require 'rails_helper'
- 1
RSpec.describe V1::PlayerSerializer, { type: :serializer } do
- 3
let(:resource) { FactoryGirl.create :player }
- 3
let(:serializer) { V1::PlayerSerializer.new(resource) }
- 1
subject do
- 2
JSON.parse(serializer.to_json, symbolize_names: true)
end
- 1
it 'should have id' do
- 1
expect(subject[:id]).to eql(resource.id)
end
- 1
it 'should have name' do
- 1
expect(subject[:name]).to eql(resource.name)
end
end
- 1
require 'rails_helper'
- 1
RSpec.describe V1::SessionSerializer, { type: :serializer } do
- 4
let(:resource) { FactoryGirl.create :user }
- 4
let(:serializer) { V1::SessionSerializer.new(resource) }
- 1
subject do
- 3
JSON.parse(serializer.to_json, symbolize_names: true)
end
- 1
it 'should have id' do
- 1
expect(subject[:id]).to eql(resource.id)
end
- 1
it 'should have username' do
- 1
expect(subject[:username]).to eql(resource.username)
end
- 1
it 'should have token' do
- 1
is_expected.to include :auth_token
end
end
- 1
require 'rails_helper'
- 1
RSpec.describe V1::TeamSerializer, { type: :serializer } do
- 7
let(:resource) { FactoryGirl.create :doubles_team }
- 7
let(:serializer) { V1::TeamSerializer.new(resource) }
- 1
subject do
- 6
JSON.parse(serializer.to_json, symbolize_names: true)
end
- 1
it 'should have id' do
- 1
expect(subject[:id]).to eql(resource.id)
end
- 1
it 'should have name' do
- 1
expect(subject[:name]).to eql(resource.name)
end
- 1
it 'should have first player id' do
- 1
expect(subject[:first_player][:id]).to eql(resource.first_player.id)
end
- 1
it 'should have first player name' do
- 1
expect(subject[:first_player][:name]).to eql(resource.first_player.name)
end
- 1
it 'should have second player id' do
- 1
expect(subject[:second_player][:id]).to eql(resource.second_player.id)
end
- 1
it 'should have second player name' do
- 1
expect(subject[:second_player][:name]).to eql(resource.second_player.name)
end
end
- 1
require 'rails_helper'
- 1
RSpec.describe V1::UserSerializer, { type: :serializer } do
- 4
let(:resource) { FactoryGirl.create :user }
- 4
let(:serializer) { V1::UserSerializer.new(resource) }
- 1
subject do
- 3
JSON.parse(serializer.to_json, symbolize_names: true)
end
- 1
it 'should have id' do
- 1
expect(subject[:id]).to eql(resource.id)
end
- 1
it 'should have username' do
- 1
expect(subject[:username]).to eql(resource.username)
end
- 1
it 'should not have token' do
- 1
is_expected.not_to include :auth_token
end
end
- 1
module Request
- 1
module JsonHelpers
- 1
def json_response
- 95
@json_response ||= JSON.parse(response.body, symbolize_names: true)
end
end
- 1
module HeadersHelpers
- 1
def api_accept_header_version(version = 1)
- 2
api_accept_header "application/tennis.scoreboard.v#{version}"
end
- 1
def api_accept_header(value)
- 3
request.headers['Accept'] = value
end
- 1
def api_authorization_header(token)
- 113
request.headers['Authorization'] = token
end
- 1
def include_default_accept_headers
- 183
api_response_format
end
# private
- 1
def api_response_format(format = Mime::JSON)
- 183
request.headers['Content-Type'] = format.to_s
end
end
end