rubocop-autocorrect #202

Merged
bustikiller merged 19 commits from rubocop-autocorrect into main 2024-12-28 18:26:28 +00:00
93 changed files with 620 additions and 350 deletions

View File

@ -24,6 +24,7 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
- uses: ruby/setup-ruby@v1.202.0 - uses: ruby/setup-ruby@v1.202.0
- run: bundle install - run: bundle install
- run: bundle exec rubocop --force-exclusion --parallel
- name: Wait until Postgres is ready to accept connections - name: Wait until Postgres is ready to accept connections
run: | run: |
apt-get update && apt-get install -f -y postgresql-client apt-get update && apt-get install -f -y postgresql-client

29
.rubocop.yml Normal file
View File

@ -0,0 +1,29 @@
require:
- rubocop-rails
- rubocop-factory_bot
- rubocop-rspec
- rubocop-rspec_rails
AllCops:
NewCops: enable
Exclude:
- 'db/**/*'
- 'config/**/*'
- 'script/**/*'
- 'bin/*'
- '*.yml'
Layout/LineLength:
Max: 120
RSpec/ExampleLength:
Enabled: false
Metrics/ModuleLength:
Enabled: false
RSpec/MultipleMemoizedHelpers:
Enabled: false
Style/Documentation:
Enabled: false
Metrics/MethodLength:
Max: 20
Rails/SkipsModelValidations:
Enabled: false
Metrics/AbcSize:
Enabled: false

20
Gemfile
View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
source 'https://rubygems.org' source 'https://rubygems.org'
ruby '3.3.6' ruby '3.3.6'
@ -15,15 +17,15 @@ gem 'stimulus-rails'
gem 'turbo-rails' gem 'turbo-rails'
gem 'tzinfo-data', platforms: %i[windows jruby] gem 'tzinfo-data', platforms: %i[windows jruby]
gem 'acts_as_tenant'
gem 'faker' gem 'faker'
gem 'httparty'
gem 'jsonapi-rails' gem 'jsonapi-rails'
gem 'pluck_to_hash'
gem 'rack-cors' gem 'rack-cors'
gem 'react-rails' gem 'react-rails'
gem 'rubytree'
gem 'acts_as_tenant'
gem 'httparty'
gem 'rswag' gem 'rswag'
gem 'pluck_to_hash' gem 'rubytree'
group :development, :test do group :development, :test do
gem 'annotaterb' gem 'annotaterb'
@ -36,12 +38,16 @@ group :development, :test do
end end
group :development do group :development do
gem 'rubocop'
gem 'web-console'
gem 'letter_opener_web' gem 'letter_opener_web'
gem 'rubocop'
gem 'rubocop-factory_bot', require: false
gem 'rubocop-rails', require: false
gem 'rubocop-rspec', require: false
gem 'rubocop-rspec_rails', require: false
gem 'web-console'
end end
gem 'chroma' gem 'chroma'
gem 'solid_queue', '~> 1.0' gem 'solid_queue', '~> 1.0'
gem "devise", "~> 4.9" gem 'devise', '~> 4.9'

View File

@ -339,6 +339,18 @@ GEM
unicode-display_width (>= 2.4.0, < 4.0) unicode-display_width (>= 2.4.0, < 4.0)
rubocop-ast (1.36.2) rubocop-ast (1.36.2)
parser (>= 3.3.1.0) parser (>= 3.3.1.0)
rubocop-factory_bot (2.26.1)
rubocop (~> 1.61)
rubocop-rails (2.28.0)
activesupport (>= 4.2.0)
rack (>= 1.1)
rubocop (>= 1.52.0, < 2.0)
rubocop-ast (>= 1.31.1, < 2.0)
rubocop-rspec (3.3.0)
rubocop (~> 1.61)
rubocop-rspec_rails (2.30.0)
rubocop (~> 1.61)
rubocop-rspec (~> 3, >= 3.0.1)
ruby-progressbar (1.13.0) ruby-progressbar (1.13.0)
rubytree (2.1.0) rubytree (2.1.0)
json (~> 2.0, > 2.3.1) json (~> 2.0, > 2.3.1)
@ -426,6 +438,10 @@ DEPENDENCIES
rspec-rails (~> 7.1.0) rspec-rails (~> 7.1.0)
rswag rswag
rubocop rubocop
rubocop-factory_bot
rubocop-rails
rubocop-rspec
rubocop-rspec_rails
rubytree rubytree
shoulda-matchers (~> 6.0) shoulda-matchers (~> 6.0)
solid_queue (~> 1.0) solid_queue (~> 1.0)
@ -561,6 +577,10 @@ CHECKSUMS
rswag-ui (2.16.0) sha256=a1f49e927dceda92e6e6e7c1000f1e217ee66c565f69e28131dc98b33cd3a04f rswag-ui (2.16.0) sha256=a1f49e927dceda92e6e6e7c1000f1e217ee66c565f69e28131dc98b33cd3a04f
rubocop (1.69.2) sha256=762fb0f30a379bf6054588d39f1815a2a1df8f067bc0337d3fe500e346924bb8 rubocop (1.69.2) sha256=762fb0f30a379bf6054588d39f1815a2a1df8f067bc0337d3fe500e346924bb8
rubocop-ast (1.36.2) sha256=566405b7f983eb9aa3b91d28aca6bc6566e356a97f59e89851dd910aef1dd1ca rubocop-ast (1.36.2) sha256=566405b7f983eb9aa3b91d28aca6bc6566e356a97f59e89851dd910aef1dd1ca
rubocop-factory_bot (2.26.1) sha256=8de13cd4edcee5ca800f255188167ecef8dbfc3d1fae9f15734e9d2e755392aa
rubocop-rails (2.28.0) sha256=4967bed9ea13e6dcab566fea4265a6dd0381db739b305e48930aba1282da2715
rubocop-rspec (3.3.0) sha256=79e1b281a689044d1516fefbc52e2e6c06cd367c25ebeaf06a7a198e9071cd7d
rubocop-rspec_rails (2.30.0) sha256=888112e83f9d7ef7ad2397e9d69a0b9614a4bae24f072c399804a180f80c4c46
ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33 ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33
rubytree (2.1.0) sha256=30e8759ba060dff0dabf7e40cbaaa4df892fa34cbe9f1b3fbb00e83a3f321e4b rubytree (2.1.0) sha256=30e8759ba060dff0dabf7e40cbaaa4df892fa34cbe9f1b3fbb00e83a3f321e4b
rubyzip (2.3.2) sha256=3f57e3935dc2255c414484fbf8d673b4909d8a6a57007ed754dde39342d2373f rubyzip (2.3.2) sha256=3f57e3935dc2255c414484fbf8d673b4909d8a6a57007ed754dde39342d2373f

View File

@ -1,6 +1,8 @@
# frozen_string_literal: true
# Add your own tasks in files placed in lib/tasks ending in .rake, # Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
require_relative "config/application" require_relative 'config/application'
Rails.application.load_tasks Rails.application.load_tasks

View File

@ -1,5 +1,7 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
module ApplicationCable module ApplicationCable
class Channel < ActionCable::Channel::Base class Channel < ActionCable::Channel::Base
end end

View File

@ -1,5 +1,7 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
module ApplicationCable module ApplicationCable
class Connection < ActionCable::Connection::Base class Connection < ActionCable::Connection::Base
end end

View File

@ -6,22 +6,27 @@ class AffinitiesController < ApplicationController
before_action :set_group before_action :set_group
def index def index
overridden_affinities = @group.affinities overridden = @group.affinities.each_with_object({}) do |affinity, acc|
.each_with_object({}) { |affinity, acc| acc[affinity.another_group(@group).id] = affinity.discomfort } acc[affinity.another_group(@group).id] = affinity.discomfort
Group.where.not(id: @group.id).pluck(:id).index_with { |group_id| GroupAffinity::MAX_DISCOMFORT - (overridden_affinities[group_id] || GroupAffinity::NEUTRAL) } end
.then { |affinities| render json: affinities } Group.where.not(id: @group.id)
.pluck(:id)
.index_with { |group_id| GroupAffinity::MAX_DISCOMFORT - (overridden[group_id] || GroupAffinity::NEUTRAL) }
.then { |affinities| render json: affinities }
end end
def bulk_update def bulk_update
params.expect(affinities: [[:group_id, :affinity]]).map(&:to_h).map do |affinity| affinities = params.expect(affinities: [%i[group_id affinity]]).map(&:to_h).map do |affinity|
{ {
group_a_id: @group.id, group_a_id: @group.id,
group_b_id: affinity[:group_id], group_b_id: affinity[:group_id],
discomfort: GroupAffinity::MAX_DISCOMFORT - affinity[:affinity] discomfort: GroupAffinity::MAX_DISCOMFORT - affinity[:affinity]
} }
end.then { |affinities| GroupAffinity.upsert_all(affinities) } end
render json: {}, status: :ok
GroupAffinity.upsert_all(affinities)
render json: {}, status: :ok
rescue ActiveRecord::InvalidForeignKey rescue ActiveRecord::InvalidForeignKey
render json: { error: 'At least one of the group IDs provided does not exist.' }, status: :bad_request render json: { error: 'At least one of the group IDs provided does not exist.' }, status: :bad_request
rescue ActiveRecord::StatementInvalid rescue ActiveRecord::StatementInvalid

View File

@ -1,5 +1,7 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
class ApplicationController < ActionController::Base class ApplicationController < ActionController::Base
set_current_tenant_through_filter set_current_tenant_through_filter
before_action :set_tenant before_action :set_tenant
@ -40,7 +42,7 @@ class ApplicationController < ActionController::Base
end end
def captcha_params def captcha_params
params.expect(captcha: [:id, :answer]) params.expect(captcha: %i[id answer])
end end
def default_url_options(options = {}) def default_url_options(options = {})
@ -53,7 +55,7 @@ class ApplicationController < ActionController::Base
def development_swagger? def development_swagger?
Rails.env.test? || Rails.env.test? ||
Rails.env.development? && request.headers['referer']&.include?('/api-docs/index.html') (Rails.env.development? && request.headers['referer']&.include?('/api-docs/index.html'))
end end
def set_csrf_cookie def set_csrf_cookie

View File

@ -1,10 +1,12 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
class CaptchaController < ApplicationController class CaptchaController < ApplicationController
skip_before_action :authenticate_user! skip_before_action :authenticate_user!
skip_before_action :set_tenant skip_before_action :set_tenant
def create def create
id = LibreCaptcha.new.get_id id = LibreCaptcha.new.id
render json: { render json: {
id:, id:,
media_url: media_captcha_index_url(id:) media_url: media_captcha_index_url(id:)

View File

@ -1,12 +1,14 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
class ExpensesController < ApplicationController class ExpensesController < ApplicationController
def summary def summary
render json: Expenses::TotalQuery.new.call render json: Expenses::TotalQuery.new.call
end end
def index def index
render json: Expense.all.order(pricing_type: :asc, amount: :desc).as_json(only: %i[id name amount pricing_type]) render json: Expense.order(pricing_type: :asc, amount: :desc).as_json(only: %i[id name amount pricing_type])
end end
def create def create

View File

@ -1,5 +1,7 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
class GroupsController < ApplicationController class GroupsController < ApplicationController
def index def index
query_result = Groups::SummaryQuery.new.call.as_json.map(&:deep_symbolize_keys).map do |group| query_result = Groups::SummaryQuery.new.call.as_json.map(&:deep_symbolize_keys).map do |group|
@ -39,6 +41,6 @@ class GroupsController < ApplicationController
end end
def group_params def group_params
params.expect(group: [:name, :icon, :color]) params.expect(group: %i[name icon color])
end end
end end

View File

@ -1,10 +1,12 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
require 'csv' require 'csv'
class GuestsController < ApplicationController class GuestsController < ApplicationController
def index def index
render json: Guest.all.includes(:group) render json: Guest.includes(:group)
.left_joins(:group) .left_joins(:group)
.order('groups.name' => :asc, name: :asc) .order('groups.name' => :asc, name: :asc)
.as_json(only: %i[id name status], include: { group: { only: %i[id name] } }) .as_json(only: %i[id name status], include: { group: { only: %i[id name] } })

View File

@ -1,29 +1,43 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
class SummaryController < ApplicationController class SummaryController < ApplicationController
def index def index
expense_summary = Expenses::TotalQuery.new(wedding: ActsAsTenant.current_tenant).call
guest_summary = Guest.group(:status).count
render json: { render json: {
expenses: { expenses:,
projected: { guests:
total: expense_summary['total_projected'], }
guests: expense_summary['projected_guests'] end
},
confirmed: { private
total: expense_summary['total_confirmed'],
guests: expense_summary['confirmed_guests'] def guests
}, guest_summary = Guest.group(:status).count
status: {
paid: 0 {
} total: guest_summary.except('considered').values.sum,
confirmed: guest_summary['confirmed'].to_i,
declined: guest_summary['declined'].to_i,
tentative: guest_summary['tentative'].to_i,
invited: guest_summary['invited'].to_i
}
end
def expenses
expense_summary = Expenses::TotalQuery.new(wedding: ActsAsTenant.current_tenant).call
{
projected: {
total: expense_summary['total_projected'],
guests: expense_summary['projected_guests']
}, },
guests: { confirmed: {
total: guest_summary.except('considered').values.sum, total: expense_summary['total_confirmed'],
confirmed: guest_summary['confirmed'].to_i, guests: expense_summary['confirmed_guests']
declined: guest_summary['declined'].to_i, },
tentative: guest_summary['tentative'].to_i, status: {
invited: guest_summary['invited'].to_i paid: 0
} }
} }
end end

View File

@ -1,8 +1,10 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
class TablesArrangementsController < ApplicationController class TablesArrangementsController < ApplicationController
def index def index
render json: TablesArrangement.all.order(discomfort: :asc).limit(3).as_json(only: %i[id name discomfort]) render json: TablesArrangement.order(discomfort: :asc).limit(3).as_json(only: %i[id name discomfort])
end end
def show def show

View File

@ -1,5 +1,7 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
class TokensController < ApplicationController class TokensController < ApplicationController
skip_before_action :authenticate_user! skip_before_action :authenticate_user!
skip_before_action :set_tenant skip_before_action :set_tenant

View File

@ -1,23 +1,27 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
class Users::ConfirmationsController < Devise::ConfirmationsController # frozen_string_literal: true
clear_respond_to
respond_to :json
def show module Users
super do |resource| class ConfirmationsController < Devise::ConfirmationsController
if resource.errors.empty? clear_respond_to
respond_to do |format| respond_to :json
format.json { render json: resource, status: :ok }
format.any { redirect_to root_path } def show
super do |resource|
if resource.errors.empty?
respond_to do |format|
format.json { render json: resource, status: :ok }
format.any { redirect_to root_path }
end
else
render json: {
message: 'Record invalid',
errors: resource.errors.full_messages
}, status: :unprocessable_entity
end end
else return
render json: {
message: 'Record invalid',
errors: resource.errors.full_messages
}, status: :unprocessable_entity
end end
return
end end
end end
end end

View File

@ -1,28 +1,32 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
class Users::RegistrationsController < Devise::RegistrationsController # frozen_string_literal: true
clear_respond_to
respond_to :json
before_action :validate_captcha!, only: :create module Users
class RegistrationsController < Devise::RegistrationsController
clear_respond_to
respond_to :json
def create before_action :validate_captcha!, only: :create
wedding = Wedding.create(slug: params[:slug])
unless wedding.persisted?
render json: { errors: wedding.errors.full_messages }, status: :unprocessable_entity
return
end
ActsAsTenant.with_tenant(wedding) do def create
super do |user| wedding = Wedding.create(slug: params[:slug])
wedding.destroy unless user.persisted? unless wedding.persisted?
render json: { errors: wedding.errors.full_messages }, status: :unprocessable_entity
return
end
ActsAsTenant.with_tenant(wedding) do
super do |user|
wedding.destroy unless user.persisted?
end
end end
end end
end
private private
def set_tenant def set_tenant
set_current_tenant(nil) set_current_tenant(nil)
end
end end
end end

View File

@ -1,6 +1,10 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
class Users::SessionsController < Devise::SessionsController # frozen_string_literal: true
clear_respond_to
respond_to :json module Users
class SessionsController < Devise::SessionsController
clear_respond_to
respond_to :json
end
end end

View File

@ -1,5 +1,7 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
module TreeNodeExtension module TreeNodeExtension
def distance_to_common_ancestor(another_node) def distance_to_common_ancestor(another_node)
return 0 if self == another_node return 0 if self == another_node

View File

@ -1,4 +1,6 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
module ApplicationHelper module ApplicationHelper
end end

View File

@ -1,4 +1,6 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
module ExpensesHelper module ExpensesHelper
end end

View File

@ -1,4 +1,6 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
module GroupsHelper module GroupsHelper
end end

View File

@ -1,4 +1,6 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
module GuestsHelper module GuestsHelper
end end

View File

@ -1,4 +1,6 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
module TablesArrangementsHelper module TablesArrangementsHelper
end end

View File

@ -1,5 +1,7 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
class ApplicationJob < ActiveJob::Base class ApplicationJob < ActiveJob::Base
# Automatically retry jobs that encountered a deadlock # Automatically retry jobs that encountered a deadlock
# retry_on ActiveRecord::Deadlocked # retry_on ActiveRecord::Deadlocked

View File

@ -1,5 +1,7 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
class TableSimulatorJob < ApplicationJob class TableSimulatorJob < ApplicationJob
queue_as :default queue_as :default

View File

@ -1,6 +1,8 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
class ApplicationMailer < ActionMailer::Base class ApplicationMailer < ActionMailer::Base
default from: "from@example.com" default from: 'from@example.com'
layout "mailer" layout 'mailer'
end end

View File

@ -1,5 +1,7 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
class ApplicationRecord < ActiveRecord::Base class ApplicationRecord < ActiveRecord::Base
primary_abstract_class primary_abstract_class
end end

View File

@ -1,5 +1,7 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
# == Schema Information # == Schema Information
# #
# Table name: expenses # Table name: expenses

View File

@ -1,5 +1,7 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
# == Schema Information # == Schema Information
# #
# Table name: groups # Table name: groups
@ -31,7 +33,7 @@ class Group < ApplicationRecord
validates :name, uniqueness: true validates :name, uniqueness: true
validates :name, :order, presence: true validates :name, :order, presence: true
has_many :children, class_name: 'Group', foreign_key: 'parent_id' has_many :children, class_name: 'Group', foreign_key: 'parent_id', dependent: :nullify, inverse_of: :parent
belongs_to :parent, class_name: 'Group', optional: true belongs_to :parent, class_name: 'Group', optional: true
before_create :set_color before_create :set_color
@ -41,10 +43,7 @@ class Group < ApplicationRecord
has_many :guests, dependent: :nullify has_many :guests, dependent: :nullify
def colorize_children(generation = 1) def colorize_children(generation = 1)
derived_colors = generation == 1 ? color.paint.palette.analogous(size: children.count) : color.paint.palette.decreasing_saturation children.zip(palette(generation)) do |child, raw_color|
children.zip(derived_colors) do |child, raw_color|
final_color = raw_color.paint final_color = raw_color.paint
final_color.brighten(60) if final_color.dark? final_color.brighten(60) if final_color.dark?
@ -60,6 +59,14 @@ class Group < ApplicationRecord
private private
def palette(generation)
if generation == 1
color.paint.palette.analogous(size: children.count)
else
color.paint.palette.decreasing_saturation
end
end
def set_color def set_color
return if color.present? return if color.present?

View File

@ -1,5 +1,7 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
# == Schema Information # == Schema Information
# #
# Table name: group_affinities # Table name: group_affinities
@ -30,7 +32,8 @@ class GroupAffinity < ApplicationRecord
belongs_to :group_a, class_name: 'Group' belongs_to :group_a, class_name: 'Group'
belongs_to :group_b, class_name: 'Group' belongs_to :group_b, class_name: 'Group'
validates :discomfort, numericality: { greater_than_or_equal_to: MIN_DISCOMFORT, less_than_or_equal_to: MAX_DISCOMFORT } validates :discomfort,
numericality: { greater_than_or_equal_to: MIN_DISCOMFORT, less_than_or_equal_to: MAX_DISCOMFORT }
def another_group(group) def another_group(group)
return nil if group != group_a && group != group_b return nil if group != group_a && group != group_b

View File

@ -1,5 +1,7 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
# == Schema Information # == Schema Information
# #
# Table name: guests # Table name: guests
@ -39,8 +41,8 @@ class Guest < ApplicationRecord
scope :potential, -> { where.not(status: %i[declined considered]) } scope :potential, -> { where.not(status: %i[declined considered]) }
after_save :recalculate_simulations, if: :saved_change_to_status?
after_destroy :recalculate_simulations after_destroy :recalculate_simulations
after_save :recalculate_simulations, if: :saved_change_to_status?
has_many :seats, dependent: :delete_all has_many :seats, dependent: :delete_all

View File

@ -1,5 +1,7 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
# == Schema Information # == Schema Information
# #
# Table name: seats # Table name: seats

View File

@ -1,5 +1,7 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
# == Schema Information # == Schema Information
# #
# Table name: tables_arrangements # Table name: tables_arrangements
@ -21,7 +23,7 @@
# #
class TablesArrangement < ApplicationRecord class TablesArrangement < ApplicationRecord
acts_as_tenant :wedding acts_as_tenant :wedding
has_many :seats has_many :seats, dependent: :delete_all
has_many :guests, through: :seats has_many :guests, through: :seats
before_create :assign_name before_create :assign_name

View File

@ -1,5 +1,7 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
# == Schema Information # == Schema Information
# #
# Table name: users # Table name: users

View File

@ -1,5 +1,7 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
# == Schema Information # == Schema Information
# #
# Table name: weddings # Table name: weddings

View File

@ -1,5 +1,7 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
module Expenses module Expenses
class TotalQuery class TotalQuery
private attr_reader :wedding private attr_reader :wedding
@ -16,7 +18,7 @@ module Expenses
private private
def query def query
<<~SQL <<~SQL.squish
WITH guest_count AS (#{guest_count_per_status}), WITH guest_count AS (#{guest_count_per_status}),
expense_summary AS (#{expense_summary}) expense_summary AS (#{expense_summary})
SELECT guest_count.confirmed as confirmed_guests, SELECT guest_count.confirmed as confirmed_guests,
@ -28,7 +30,7 @@ module Expenses
end end
def expense_summary def expense_summary
<<~SQL <<~SQL.squish
SELECT coalesce(sum(amount) filter (where pricing_type = 'fixed'), 0) as fixed, SELECT coalesce(sum(amount) filter (where pricing_type = 'fixed'), 0) as fixed,
coalesce(sum(amount) filter (where pricing_type = 'per_person'), 0) as variable coalesce(sum(amount) filter (where pricing_type = 'per_person'), 0) as variable
FROM expenses FROM expenses
@ -37,7 +39,7 @@ module Expenses
end end
def guest_count_per_status def guest_count_per_status
<<~SQL <<~SQL.squish
SELECT COALESCE(count(*) filter(where status = #{Guest.statuses['confirmed']}), 0) as confirmed, SELECT COALESCE(count(*) filter(where status = #{Guest.statuses['confirmed']}), 0) as confirmed,
COALESCE(count(*) filter(where status IN (#{Guest.statuses.values_at('confirmed', 'invited', 'tentative').join(',')})), 0) as projected COALESCE(count(*) filter(where status IN (#{Guest.statuses.values_at('confirmed', 'invited', 'tentative').join(',')})), 0) as projected
FROM guests FROM guests

View File

@ -1,5 +1,7 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
module Groups module Groups
class SummaryQuery class SummaryQuery
def call def call
@ -9,13 +11,21 @@ module Groups
:icon, :icon,
:parent_id, :parent_id,
:color, :color,
*count_expressions
)
end
private
def count_expressions
[
Arel.sql('count(*) filter (where status IS NOT NULL) as total'), Arel.sql('count(*) filter (where status IS NOT NULL) as total'),
Arel.sql('count(*) filter (where status = 0) as considered'), Arel.sql('count(*) filter (where status = 0) as considered'),
Arel.sql('count(*) filter (where status = 10) as invited'), Arel.sql('count(*) filter (where status = 10) as invited'),
Arel.sql('count(*) filter (where status = 20) as confirmed'), Arel.sql('count(*) filter (where status = 20) as confirmed'),
Arel.sql('count(*) filter (where status = 30) as declined'), Arel.sql('count(*) filter (where status = 30) as declined'),
Arel.sql('count(*) filter (where status = 40) as tentative'), Arel.sql('count(*) filter (where status = 40) as tentative')
) ]
end end
end end
end end

View File

@ -1,5 +1,7 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
class SerializableGroup < JSONAPI::Serializable::Resource class SerializableGroup < JSONAPI::Serializable::Resource
type 'group' type 'group'

View File

@ -1,5 +1,7 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
class SerializableGuest < JSONAPI::Serializable::Resource class SerializableGuest < JSONAPI::Serializable::Resource
type 'guest' type 'guest'

View File

@ -1,5 +1,7 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
class AffinityGroupsHierarchy < Array class AffinityGroupsHierarchy < Array
include Singleton include Singleton

View File

@ -1,20 +1,20 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
class LibreCaptcha class LibreCaptcha
def get_id def id
HTTParty.post("http://libre-captcha:8888/v2/captcha", HTTParty.post('http://libre-captcha:8888/v2/captcha',
body: { body: {
input_type: "text", input_type: 'text',
level: :hard, level: :hard,
media: 'image/png', media: 'image/png',
size: '350x100' size: '350x100'
}.to_json }.to_json).then { |raw| JSON.parse(raw)['id'] }
).then { |raw| JSON.parse(raw)['id'] }
end end
def valid?(id:, answer:) def valid?(id:, answer:)
HTTParty.post("http://libre-captcha:8888/v2/answer", HTTParty.post('http://libre-captcha:8888/v2/answer',
body: { id:, answer: }.to_json body: { id:, answer: }.to_json).then { |raw| JSON.parse(raw)['result'] == 'True' }
).then { |raw| JSON.parse(raw)['result'] == 'True' }
end end
end end

View File

@ -1,5 +1,7 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
module Tables module Tables
class DiscomfortCalculator class DiscomfortCalculator
class << self class << self
@ -7,6 +9,7 @@ module Tables
distance = AffinityGroupsHierarchy.instance.distance(id_a, id_b) distance = AffinityGroupsHierarchy.instance.distance(id_a, id_b)
return 1 if distance.nil? return 1 if distance.nil?
Rational(distance, distance + 1) Rational(distance, distance + 1)
end end
end end

View File

@ -1,5 +1,7 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
require_relative '../../extensions/tree_node_extension' require_relative '../../extensions/tree_node_extension'
module Tables module Tables

View File

@ -1,5 +1,7 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
module Tables module Tables
class Shift class Shift
private attr_reader :initial_solution private attr_reader :initial_solution

View File

@ -1,5 +1,7 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
module Tables module Tables
class Swap class Swap
private attr_reader :initial_solution private attr_reader :initial_solution

View File

@ -1,5 +1,7 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
module Tables module Tables
class Table < Set class Table < Set
attr_accessor :discomfort, :min_per_table, :max_per_table attr_accessor :discomfort, :min_per_table, :max_per_table

View File

@ -1,5 +1,7 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
module VNS module VNS
class Engine class Engine
class << self class << self

View File

@ -1,6 +1,8 @@
# frozen_string_literal: true
# This file is used by Rack-based servers to start the application. # This file is used by Rack-based servers to start the application.
require_relative "config/environment" require_relative 'config/environment'
run Rails.application run Rails.application
Rails.application.load_server Rails.application.load_server

View File

@ -1,8 +1,10 @@
# frozen_string_literal: true
# This rake task was added by annotate_rb gem. # This rake task was added by annotate_rb gem.
# Can set `ANNOTATERB_SKIP_ON_DB_TASKS` to be anything to skip this # Can set `ANNOTATERB_SKIP_ON_DB_TASKS` to be anything to skip this
if Rails.env.development? && ENV["ANNOTATERB_SKIP_ON_DB_TASKS"].nil? if Rails.env.development? && ENV['ANNOTATERB_SKIP_ON_DB_TASKS'].nil?
require "annotate_rb" require 'annotate_rb'
AnnotateRb::Core.load_rake_tasks AnnotateRb::Core.load_rake_tasks
end end

View File

@ -1,70 +1,72 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
require 'rails_helper' require 'rails_helper'
module Tree module Tree
RSpec.describe TreeNode do RSpec.describe TreeNode do
describe '#distance_to_common_ancestor' do describe '#distance_to_common_ancestor' do
def assert_distance(node_1, node_2, distance) def assert_distance(node1, node2, distance)
aggregate_failures do aggregate_failures do
expect(node_1.distance_to_common_ancestor(node_2)).to eq(distance) expect(node1.distance_to_common_ancestor(node2)).to eq(distance)
expect(node_2.distance_to_common_ancestor(node_1)).to eq(distance) expect(node2.distance_to_common_ancestor(node1)).to eq(distance)
end end
end end
context 'when the two nodes are the same' do context 'when the two nodes are the same' do
it 'returns 0 when comparing the root itself' do it 'returns 0 when comparing the root itself' do
root = Tree::TreeNode.new('root') root = described_class.new('root')
assert_distance(root, root, 0) assert_distance(root, root, 0)
end end
it 'returns 0 when comparing a child to itself' do it 'returns 0 when comparing a child to itself' do
root = Tree::TreeNode.new('root') root = described_class.new('root')
child = root << Tree::TreeNode.new('child') child = root << described_class.new('child')
assert_distance(child, child, 0) assert_distance(child, child, 0)
end end
end end
context 'when the two nodes are siblings' do context 'when the two nodes are siblings' do
it 'returns 1 when comparing siblings' do it 'returns 1 when comparing siblings' do
root = Tree::TreeNode.new('root') root = described_class.new('root')
child1 = root << Tree::TreeNode.new('child1') child1 = root << described_class.new('child1')
child2 = root << Tree::TreeNode.new('child2') child2 = root << described_class.new('child2')
assert_distance(child1, child2, 1) assert_distance(child1, child2, 1)
end end
end end
context 'when one node is parent of the other' do context 'when one node is parent of the other' do
it 'returns 1 when comparing parent to child' do it 'returns 1 when comparing parent to child' do
root = Tree::TreeNode.new('root') root = described_class.new('root')
child = root << Tree::TreeNode.new('child') child = root << described_class.new('child')
assert_distance(root, child, 1) assert_distance(root, child, 1)
end end
end end
context 'when one node is grandparent of the other' do context 'when one node is grandparent of the other' do
it 'returns 2 when comparing grandparent to grandchild' do it 'returns 2 when comparing grandparent to grandchild' do
root = Tree::TreeNode.new('root') root = described_class.new('root')
child = root << Tree::TreeNode.new('child') child = root << described_class.new('child')
grandchild = child << Tree::TreeNode.new('grandchild') grandchild = child << described_class.new('grandchild')
assert_distance(root, grandchild, 2) assert_distance(root, grandchild, 2)
end end
end end
context 'when the two nodes are cousins' do context 'when the two nodes are cousins' do
it 'returns 2 when comparing cousins' do it 'returns 2 when comparing cousins' do
root = Tree::TreeNode.new('root') root = described_class.new('root')
child1 = root << Tree::TreeNode.new('child1') child1 = root << described_class.new('child1')
child2 = root << Tree::TreeNode.new('child2') child2 = root << described_class.new('child2')
grandchild1 = child1 << Tree::TreeNode.new('grandchild1') grandchild1 = child1 << described_class.new('grandchild1')
grandchild2 = child2 << Tree::TreeNode.new('grandchild2') grandchild2 = child2 << described_class.new('grandchild2')
assert_distance(grandchild1, grandchild2, 2) assert_distance(grandchild1, grandchild2, 2)
end end
end end
context 'when the two nodes are not related' do context 'when the two nodes are not related' do
it 'returns nil' do it 'returns nil' do
root = Tree::TreeNode.new('root') root = described_class.new('root')
another_root = Tree::TreeNode.new('another_root') another_root = described_class.new('another_root')
assert_distance(root, another_root, nil) assert_distance(root, another_root, nil)
end end
end end

View File

@ -1,19 +1,22 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
FactoryBot.define do FactoryBot.define do
factory :expense do factory :expense do
wedding wedding
sequence(:name) { |i| "Expense #{i}" } sequence(:name) { |i| "Expense #{i}" }
pricing_type { "fixed" } pricing_type { 'fixed' }
amount { 100 } amount { 100 }
end
trait :fixed do
pricing_type { "fixed" }
end
trait :per_person do
pricing_type { "per_person" }
end
end end
trait :fixed do
pricing_type { 'fixed' }
end
trait :per_person do
pricing_type { 'per_person' }
end
end

View File

@ -1,9 +1,13 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
FactoryBot.define do FactoryBot.define do
factory :group_affinity do factory :group_affinity do
association :group_a, factory: :group group_a factory: %i[group]
association :group_b, factory: :group group_b factory: %i[group]
discomfort { GroupAffinity::NEUTRAL } discomfort { GroupAffinity::NEUTRAL }
end end
end end

View File

@ -1,5 +1,9 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
FactoryBot.define do FactoryBot.define do
factory :group do factory :group do
wedding wedding

View File

@ -1,5 +1,9 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
FactoryBot.define do FactoryBot.define do
factory :guest do factory :guest do
group group

View File

@ -1,8 +1,11 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
FactoryBot.define do FactoryBot.define do
factory :tables_arrangement do factory :tables_arrangement do
wedding wedding
end end
end end

View File

@ -1,5 +1,9 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
FactoryBot.define do FactoryBot.define do
factory :user do factory :user do
wedding wedding

View File

@ -1,5 +1,9 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
FactoryBot.define do FactoryBot.define do
factory :wedding do factory :wedding do
sequence(:slug) { |i| "wedding-#{i}" } sequence(:slug) { |i| "wedding-#{i}" }

View File

@ -1,12 +1,16 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
require 'rails_helper' require 'rails_helper'
RSpec.describe Expense, type: :model do RSpec.describe Expense do
describe 'validations' do describe 'validations' do
it { should validate_presence_of(:name) } it { is_expected.to validate_presence_of(:name) }
it { should validate_presence_of(:amount) } it { is_expected.to validate_presence_of(:amount) }
it { should validate_numericality_of(:amount).is_greater_than(0) } it { is_expected.to validate_numericality_of(:amount).is_greater_than(0) }
it { should validate_presence_of(:pricing_type) } it { is_expected.to validate_presence_of(:pricing_type) }
end end
end end

View File

@ -4,16 +4,20 @@
require 'rails_helper' require 'rails_helper'
RSpec.describe GroupAffinity, type: :model do RSpec.describe GroupAffinity do
subject(:affinity) { build(:group_affinity, group_a:, group_b:) }
let(:wedding) { create(:wedding) } let(:wedding) { create(:wedding) }
let(:group_a) { create(:group, wedding:) } let(:group_a) { create(:group, wedding:) }
let(:group_b) { create(:group, wedding:) } let(:group_b) { create(:group, wedding:) }
let(:group_c) { create(:group, wedding:) } let(:group_c) { create(:group, wedding:) }
subject { build(:group_affinity, group_a:, group_b: ) }
describe 'validations' do describe 'validations' do
it { should validate_numericality_of(:discomfort).is_greater_than_or_equal_to(0).is_less_than_or_equal_to(2) } it do
expect(affinity).to validate_numericality_of(:discomfort)
.is_greater_than_or_equal_to(0)
.is_less_than_or_equal_to(2)
end
end end
describe '.create' do describe '.create' do

View File

@ -1,10 +1,14 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
require 'rails_helper' require 'rails_helper'
RSpec.describe Group, type: :model do RSpec.describe Group do
describe 'callbacks' do describe 'callbacks' do
it 'should set color before create' do it 'sets color before create' do
expect(create(:group).color).to be_present expect(create(:group).color).to be_present
end end
end end

View File

@ -1,12 +1,17 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
require 'rails_helper' require 'rails_helper'
RSpec.describe Guest, type: :model do RSpec.describe Guest do
describe 'validations' do describe 'validations' do
it { should validate_presence_of(:name) } subject(:guest) { build(:guest) }
it { is_expected.to validate_presence_of(:name) }
it do it do
should define_enum_for(:status).with_values( expect(guest).to define_enum_for(:status).with_values(
considered: 0, considered: 0,
invited: 10, invited: 10,
confirmed: 20, confirmed: 20,
@ -16,7 +21,7 @@ RSpec.describe Guest, type: :model do
end end
end end
it { should belong_to(:group).optional } it { is_expected.to belong_to(:group).optional }
describe 'scopes' do describe 'scopes' do
describe '.potential' do describe '.potential' do
@ -27,7 +32,7 @@ RSpec.describe Guest, type: :model do
confirmed_guest = create(:guest, status: :confirmed) confirmed_guest = create(:guest, status: :confirmed)
tentative_guest = create(:guest, status: :tentative) tentative_guest = create(:guest, status: :tentative)
expect(Guest.potential).to match_array([invited_guest, confirmed_guest, tentative_guest]) expect(described_class.potential).to contain_exactly(invited_guest, confirmed_guest, tentative_guest)
end end
end end
end end

View File

@ -1,7 +1,11 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
require 'rails_helper' require 'rails_helper'
RSpec.describe Seat, type: :model do RSpec.describe Seat do
pending "add some examples to (or delete) #{__FILE__}" pending "add some examples to (or delete) #{__FILE__}"
end end

View File

@ -1,8 +1,12 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
require 'rails_helper' require 'rails_helper'
RSpec.describe TablesArrangement, type: :model do RSpec.describe TablesArrangement do
describe 'callbacks' do describe 'callbacks' do
it 'assigns a name before creation' do it 'assigns a name before creation' do
expect(create(:tables_arrangement).name).to be_present expect(create(:tables_arrangement).name).to be_present

View File

@ -1,7 +1,11 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
require 'rails_helper' require 'rails_helper'
RSpec.describe User, type: :model do RSpec.describe User do
pending "add some examples to (or delete) #{__FILE__}" pending "add some examples to (or delete) #{__FILE__}"
end end

View File

@ -1,22 +1,27 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
require 'rails_helper' require 'rails_helper'
RSpec.describe Wedding, type: :model do RSpec.describe Wedding do
describe 'validations' do describe 'validations' do
subject { build(:wedding) } subject { build(:wedding) }
describe 'slug' do
it { should allow_value('foo').for(:slug) }
it { should allow_value('foo-bar').for(:slug) }
it { should allow_value('foo-123').for(:slug) }
it { should allow_value('foo-123-').for(:slug) }
it { should allow_value('foo--123').for(:slug) }
it { should_not allow_value('Foo').for(:slug) } describe 'slug' do
it { should_not allow_value('/foo').for(:slug) } it { is_expected.to allow_value('foo').for(:slug) }
it { should_not allow_value('foo/123').for(:slug) } it { is_expected.to allow_value('foo-bar').for(:slug) }
it { should_not allow_value('foo_123').for(:slug) } it { is_expected.to allow_value('foo-123').for(:slug) }
it { should_not allow_value('foo/').for(:slug) } it { is_expected.to allow_value('foo-123-').for(:slug) }
it { is_expected.to allow_value('foo--123').for(:slug) }
it { is_expected.not_to allow_value('Foo').for(:slug) }
it { is_expected.not_to allow_value('/foo').for(:slug) }
it { is_expected.not_to allow_value('foo/123').for(:slug) }
it { is_expected.not_to allow_value('foo_123').for(:slug) }
it { is_expected.not_to allow_value('foo/').for(:slug) }
end end
end end
end end

View File

@ -1,5 +1,7 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
require 'rails_helper' require 'rails_helper'
module Expenses module Expenses
@ -61,8 +63,8 @@ module Expenses
end end
it 'returns the sum of fixed and variable expenses', :aggregate_failures do it 'returns the sum of fixed and variable expenses', :aggregate_failures do
expect(response['total_confirmed']).to eq(100 + 200 + 50 * 2) expect(response['total_confirmed']).to eq(100 + 200 + (50 * 2))
expect(response['total_projected']).to eq(100 + 200 + 11 * 50) expect(response['total_projected']).to eq(100 + 200 + (11 * 50))
expect(response['confirmed_guests']).to eq(2) expect(response['confirmed_guests']).to eq(2)
expect(response['projected_guests']).to eq(2 + 4 + 5) expect(response['projected_guests']).to eq(2 + 4 + 5)
end end

View File

@ -1,11 +1,13 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
require 'rails_helper' require 'rails_helper'
module Groups module Groups
RSpec.describe SummaryQuery do RSpec.describe SummaryQuery do
describe '#call' do describe '#call' do
subject { described_class.new.call } subject(:result) { described_class.new.call }
context 'when there are no groups' do context 'when there are no groups' do
it { is_expected.to eq([]) } it { is_expected.to eq([]) }
@ -17,7 +19,7 @@ module Groups
context 'when there are no guests' do context 'when there are no guests' do
it 'returns the summary of groups' do it 'returns the summary of groups' do
is_expected.to contain_exactly( expect(result).to contain_exactly(
{ 'id' => parent.id, { 'id' => parent.id,
'name' => 'Friends', 'name' => 'Friends',
'icon' => 'icon-1', 'icon' => 'icon-1',
@ -58,11 +60,11 @@ module Groups
create_list(:guest, 8, group: child, status: :invited) create_list(:guest, 8, group: child, status: :invited)
create_list(:guest, 9, group: child, status: :confirmed) create_list(:guest, 9, group: child, status: :confirmed)
create_list(:guest, 10, group: child, status: :declined) create_list(:guest, 10, group: child, status: :declined)
create_list(:guest, 11, group: child, status: :tentative) create_list(:guest, 11, group: child, status: :tentative) # rubocop:disable FactoryBot/ExcessiveCreateList
end end
it 'returns the summary of groups' do it 'returns the summary of groups' do
is_expected.to contain_exactly( expect(result).to contain_exactly(
{ {
'id' => parent.id, 'id' => parent.id,
'name' => 'Friends', 'name' => 'Friends',

View File

@ -1,11 +1,13 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
# This file is copied to spec/ when you run 'rails generate rspec:install' # This file is copied to spec/ when you run 'rails generate rspec:install'
require 'spec_helper' require 'spec_helper'
ENV['RAILS_ENV'] ||= 'test' ENV['RAILS_ENV'] ||= 'test'
require_relative '../config/environment' require_relative '../config/environment'
# Prevent database truncation if the environment is production # Prevent database truncation if the environment is production
abort("The Rails environment is running in production mode!") if Rails.env.production? abort('The Rails environment is running in production mode!') if Rails.env.production?
require 'rspec/rails' require 'rspec/rails'
# Add additional requires below this line. Rails is not loaded until this point! # Add additional requires below this line. Rails is not loaded until this point!

View File

@ -4,7 +4,7 @@
require 'swagger_helper' require 'swagger_helper'
RSpec.describe 'affinities', type: :request do RSpec.describe 'affinities' do
path '/{slug}/groups/{group_id}/affinities' do path '/{slug}/groups/{group_id}/affinities' do
parameter Swagger::Schema::SLUG parameter Swagger::Schema::SLUG
parameter name: 'group_id', in: :path, type: :string, format: :uuid, description: 'group_id' parameter name: 'group_id', in: :path, type: :string, format: :uuid, description: 'group_id'
@ -46,7 +46,7 @@ RSpec.describe 'affinities', type: :request do
} }
} }
response_empty_200 response_empty200
end end
end end
end end

View File

@ -1,10 +1,11 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
require 'swagger_helper' require 'swagger_helper'
RSpec.describe 'captcha', type: :request do RSpec.describe 'captcha' do
path '/captcha' do path '/captcha' do
post('create a CAPTCHA challenge') do post('create a CAPTCHA challenge') do
tags 'CAPTCHA' tags 'CAPTCHA'
consumes 'application/json' consumes 'application/json'
@ -12,11 +13,11 @@ RSpec.describe 'captcha', type: :request do
response(201, 'created') do response(201, 'created') do
schema type: :object, schema type: :object,
required: %i[id], required: %i[id],
properties: { properties: {
id: { type: :string, format: :uuid }, id: { type: :string, format: :uuid },
media_url: { type: :string, format: :uri }, media_url: { type: :string, format: :uri }
} }
xit xit
end end
end end

View File

@ -1,8 +1,10 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
require 'swagger_helper' require 'swagger_helper'
RSpec.describe 'expenses', type: :request do RSpec.describe 'expenses' do
path '/{slug}/expenses' do path '/{slug}/expenses' do
get('list expenses') do get('list expenses') do
tags 'Expenses' tags 'Expenses'
@ -42,8 +44,8 @@ RSpec.describe 'expenses', type: :request do
} }
} }
response_empty_201 response_empty201
response_422 response422
regular_api_responses regular_api_responses
end end
end end
@ -60,9 +62,9 @@ RSpec.describe 'expenses', type: :request do
properties: Swagger::Schema::EXPENSE properties: Swagger::Schema::EXPENSE
} }
response_empty_200 response_empty200
response_422 response422
response_404 response404
regular_api_responses regular_api_responses
end end
@ -71,8 +73,8 @@ RSpec.describe 'expenses', type: :request do
produces 'application/json' produces 'application/json'
parameter Swagger::Schema::SLUG parameter Swagger::Schema::SLUG
parameter Swagger::Schema::ID parameter Swagger::Schema::ID
response_empty_200 response_empty200
response_404 response404
regular_api_responses regular_api_responses
end end
end end

View File

@ -1,8 +1,10 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
require 'swagger_helper' require 'swagger_helper'
RSpec.describe 'groups', type: :request do RSpec.describe 'groups' do
path '/{slug}/groups' do path '/{slug}/groups' do
get('list groups') do get('list groups') do
tags 'Groups' tags 'Groups'
@ -10,29 +12,29 @@ RSpec.describe 'groups', type: :request do
parameter Swagger::Schema::SLUG parameter Swagger::Schema::SLUG
response(200, 'successful') do response(200, 'successful') do
schema type: :array, schema type: :array,
items: { items: {
type: :object, type: :object,
required: %i[id name icon parent_id color attendance], required: %i[id name icon parent_id color attendance],
properties: { properties: {
id: { type: :string, format: :uuid, required: true }, id: { type: :string, format: :uuid, required: true },
name: { type: :string }, name: { type: :string },
icon: { type: :string, example: 'pi pi-crown', description: 'The CSS classes used by the icon' }, icon: { type: :string, example: 'pi pi-crown', description: 'The CSS classes used by the icon' },
parent_id: { type: :string, format: :uuid }, parent_id: { type: :string, format: :uuid },
color: { type: :string, pattern: '^#(?:[0-9a-fA-F]{3}){1,2}$' }, color: { type: :string, pattern: '^#(?:[0-9a-fA-F]{3}){1,2}$' },
attendance: { attendance: {
type: :object, type: :object,
required: %i[total considered invited confirmed declined tentative], required: %i[total considered invited confirmed declined tentative],
properties: { properties: {
total: { type: :integer, minimum: 0, description: 'Total number of guests in any status' }, total: { type: :integer, minimum: 0, description: 'Total number of guests in any status' },
considered: { type: :integer, minimum: 0 }, considered: { type: :integer, minimum: 0 },
invited: { type: :integer, minimum: 0 }, invited: { type: :integer, minimum: 0 },
confirmed: { type: :integer, minimum: 0 }, confirmed: { type: :integer, minimum: 0 },
declined: { type: :integer, minimum: 0 }, declined: { type: :integer, minimum: 0 },
tentative: { type: :integer, minimum: 0 } tentative: { type: :integer, minimum: 0 }
} }
} }
}
} }
}
xit xit
end end
regular_api_responses regular_api_responses
@ -100,7 +102,7 @@ RSpec.describe 'groups', type: :request do
parameter Swagger::Schema::SLUG parameter Swagger::Schema::SLUG
parameter name: :id, in: :path, type: :string, format: :uuid parameter name: :id, in: :path, type: :string, format: :uuid
response_empty_200 response_empty200
regular_api_responses regular_api_responses
end end
end end

View File

@ -1,8 +1,10 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
require 'swagger_helper' require 'swagger_helper'
RSpec.describe 'guests', type: :request do RSpec.describe 'guests' do
path '/{slug}/guests' do path '/{slug}/guests' do
get('list guests') do get('list guests') do
tags 'Guests' tags 'Guests'
@ -51,8 +53,8 @@ RSpec.describe 'guests', type: :request do
} }
} }
response_empty_201 response_empty201
response_422 response422
regular_api_responses regular_api_responses
end end
end end
@ -80,9 +82,9 @@ RSpec.describe 'guests', type: :request do
} }
} }
response_empty_200 response_empty200
response_422 response422
response_404 response404
regular_api_responses regular_api_responses
end end
@ -92,8 +94,8 @@ RSpec.describe 'guests', type: :request do
parameter Swagger::Schema::SLUG parameter Swagger::Schema::SLUG
parameter name: 'id', in: :path, type: :string, format: :uuid parameter name: 'id', in: :path, type: :string, format: :uuid
response_empty_200 response_empty200
response_404 response404
regular_api_responses regular_api_responses
end end
end end

View File

@ -1,5 +1,7 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
module Swagger module Swagger
module Schema module Schema
USER = { USER = {
@ -7,13 +9,13 @@ module Swagger
email: { type: :string, format: :email }, email: { type: :string, format: :email },
created_at: SwaggerResponseHelper::TIMESTAMP, created_at: SwaggerResponseHelper::TIMESTAMP,
updated_at: SwaggerResponseHelper::TIMESTAMP updated_at: SwaggerResponseHelper::TIMESTAMP
} }.freeze
ID = { ID = { # rubocop:disable Style/MutableConstant -- rswag modifies in: :path parameters
name: 'id', name: 'id',
in: :path, in: :path,
type: :string, type: :string,
format: :uuid, format: :uuid
} }
GROUP = { GROUP = {
@ -21,15 +23,15 @@ module Swagger
icon: { type: :string, example: 'pi pi-crown', description: 'The CSS classes used by the icon' }, icon: { type: :string, example: 'pi pi-crown', description: 'The CSS classes used by the icon' },
parent_id: { type: :string, format: :uuid }, parent_id: { type: :string, format: :uuid },
color: { type: :string, pattern: '^#(?:[0-9a-fA-F]{3}){1,2}$' } color: { type: :string, pattern: '^#(?:[0-9a-fA-F]{3}){1,2}$' }
} }.freeze
EXPENSE = { EXPENSE = {
name: { type: :string }, name: { type: :string },
amount: { type: :number, minimum: 0 }, amount: { type: :number, minimum: 0 },
pricing_type: { type: :string, enum: Expense.pricing_types.keys } pricing_type: { type: :string, enum: Expense.pricing_types.keys }
} }.freeze
SLUG = { SLUG = { # rubocop:disable Style/MutableConstant -- rswag modifies in: :path parameters
name: 'slug', name: 'slug',
in: :path, in: :path,
type: :string, type: :string,
@ -47,6 +49,6 @@ module Swagger
answer: { type: :string } answer: { type: :string }
} }
} }
} }.freeze
end end
end end

View File

@ -1,8 +1,10 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
require 'swagger_helper' require 'swagger_helper'
RSpec.describe 'summary', type: :request do RSpec.describe 'summary' do
path '/{slug}/summary' do path '/{slug}/summary' do
get('list summaries') do get('list summaries') do
tags 'Summary' tags 'Summary'

View File

@ -1,8 +1,10 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
require 'swagger_helper' require 'swagger_helper'
RSpec.describe 'tables_arrangements', type: :request do RSpec.describe 'tables_arrangements' do
path '/{slug}/tables_arrangements' do path '/{slug}/tables_arrangements' do
get('list tables arrangements') do get('list tables arrangements') do
tags 'Tables Arrangements' tags 'Tables Arrangements'

View File

@ -1,15 +1,5 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
require 'swagger_helper' require 'swagger_helper'
RSpec.describe 'tokens', type: :request do
path '/token' do
get('get a cookie with CSRF token') do
tags 'CSRF token'
consumes 'application/json'
produces 'application/json'
response_empty_200
end
end
end

View File

@ -1,9 +1,10 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
require 'swagger_helper' require 'swagger_helper'
RSpec.describe 'users/confirmations', type: :request do RSpec.describe 'users/confirmations' do
path '/{slug}/users/confirmation' do path '/{slug}/users/confirmation' do
get('confirm user email') do get('confirm user email') do
tags 'Users' tags 'Users'
@ -17,7 +18,7 @@ RSpec.describe 'users/confirmations', type: :request do
xit xit
end end
response_422 response422
end end
end end
end end

View File

@ -1,9 +1,10 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
require 'swagger_helper' require 'swagger_helper'
RSpec.describe 'users/registrations', type: :request do RSpec.describe 'users/registrations' do
path '/{slug}/users' do path '/{slug}/users' do
post('create registration') do post('create registration') do
tags 'Users Registrations' tags 'Users Registrations'
@ -13,13 +14,13 @@ RSpec.describe 'users/registrations', type: :request do
parameter Swagger::Schema::SLUG parameter Swagger::Schema::SLUG
parameter name: :body, in: :body, schema: { parameter name: :body, in: :body, schema: {
type: :object, type: :object,
required: [:user, :wedding], required: %i[user wedding],
properties: { properties: {
user: { user: {
type: :object, type: :object,
required: %i[email password password_confirmation], required: %i[email password password_confirmation],
properties: { properties: {
email: { type: :string, format: :email}, email: { type: :string, format: :email },
password: SwaggerResponseHelper::PASSWORD, password: SwaggerResponseHelper::PASSWORD,
password_confirmation: SwaggerResponseHelper::PASSWORD password_confirmation: SwaggerResponseHelper::PASSWORD
} }

View File

@ -1,11 +1,11 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
require 'swagger_helper' require 'swagger_helper'
RSpec.describe 'users/sessions', type: :request do RSpec.describe 'users/sessions' do
path '/{slug}/users/sign_in' do path '/{slug}/users/sign_in' do
post('create session') do post('create session') do
tags 'Users Sessions' tags 'Users Sessions'
consumes 'application/json' consumes 'application/json'
@ -32,7 +32,7 @@ RSpec.describe 'users/sessions', type: :request do
xit xit
end end
response_401(message: 'Invalid Email or password.') response401(message: 'Invalid Email or password.')
end end
end end

View File

@ -1,5 +1,7 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
require 'rails_helper' require 'rails_helper'
module Tables module Tables
RSpec.describe DiscomfortCalculator do RSpec.describe DiscomfortCalculator do
@ -12,8 +14,7 @@ module Tables
describe '#calculate' do describe '#calculate' do
before do before do
allow(calculator).to receive(:table_size_penalty).and_return(2) allow(calculator).to receive_messages(table_size_penalty: 2, cohesion_discomfort: 3)
allow(calculator).to receive(:cohesion_discomfort).and_return(3)
end end
let(:table) { Table.new(create_list(:guest, 6)) } let(:table) { Table.new(create_list(:guest, 6)) }
@ -29,6 +30,7 @@ module Tables
table.min_per_table = 5 table.min_per_table = 5
table.max_per_table = 7 table.max_per_table = 7
end end
context 'when the number of guests is in the lower bound' do context 'when the number of guests is in the lower bound' do
let(:table) { Table.new(create_list(:guest, 5)) } let(:table) { Table.new(create_list(:guest, 5)) }
@ -88,6 +90,7 @@ module Tables
allow(AffinityGroupsHierarchy.instance).to receive(:distance).with(friends.id, school.id).and_return(4) allow(AffinityGroupsHierarchy.instance).to receive(:distance).with(friends.id, school.id).and_return(4)
allow(AffinityGroupsHierarchy.instance).to receive(:distance).with(work.id, school.id).and_return(5) allow(AffinityGroupsHierarchy.instance).to receive(:distance).with(work.id, school.id).and_return(5)
end end
context 'when the table contains just two guests' do context 'when the table contains just two guests' do
context 'when they belong to the same group' do context 'when they belong to the same group' do
let(:table) { create_list(:guest, 2, group: family) } let(:table) { create_list(:guest, 2, group: family) }
@ -102,6 +105,7 @@ module Tables
create(:guest, group: friends) create(:guest, group: friends)
] ]
end end
it { expect(calculator.send(:cohesion_discomfort)).to eq(1) } it { expect(calculator.send(:cohesion_discomfort)).to eq(1) }
end end
@ -205,7 +209,7 @@ module Tables
end end
it 'returns the sum of the penalties for each pair of guests' do it 'returns the sum of the penalties for each pair of guests' do
expect(calculator.send(:cohesion_discomfort)).to eq(4 * 1 + 4 * Rational(1, 2) + 4 * Rational(2, 3)) expect(calculator.send(:cohesion_discomfort)).to eq((4 * 1) + (4 * Rational(1, 2)) + (4 * Rational(2, 3)))
end end
end end
@ -219,7 +223,7 @@ module Tables
end end
it 'returns the sum of the penalties for each pair of guests' do it 'returns the sum of the penalties for each pair of guests' do
expect(calculator.send(:cohesion_discomfort)).to eq(6 * 1 + 2 * Rational(1, 2) + 3 * Rational(2, 3)) expect(calculator.send(:cohesion_discomfort)).to eq((6 * 1) + (2 * Rational(1, 2)) + (3 * Rational(2, 3)))
end end
end end
end end

View File

@ -1,23 +1,25 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
require 'rails_helper' require 'rails_helper'
module Tables module Tables
RSpec.describe Distribution do RSpec.describe Distribution do
describe '#random_distribution' do describe '#random_distribution' do
let(:subject) { described_class.new(min_per_table: 5, max_per_table: 10) } subject(:distribution) { described_class.new(min_per_table: 5, max_per_table: 10) }
context 'when there are fewer people than the minimum per table' do context 'when there are fewer people than the minimum per table' do
it 'creates one table' do it 'creates one table' do
subject.random_distribution([1, 2, 3, 4]) distribution.random_distribution([1, 2, 3, 4])
expect(subject.tables.count).to eq(1) expect(distribution.tables.count).to eq(1)
end end
end end
context 'when there are more people than the maximum per table' do context 'when there are more people than the maximum per table' do
it 'creates multiple tables' do it 'creates multiple tables' do
subject.random_distribution([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) distribution.random_distribution([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
expect(subject.tables.count).to be > 1 expect(distribution.tables.count).to be > 1
end end
end end
end end

View File

@ -1,5 +1,7 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
require 'rails_helper' require 'rails_helper'
module Tables module Tables
@ -7,7 +9,7 @@ module Tables
describe '#each' do describe '#each' do
let(:shifts) do let(:shifts) do
acc = [] acc = []
described_class.new(initial_solution).each do |solution| described_class.new(initial_solution).each do |solution| # rubocop:disable Style/MapIntoArray -- #map is not implemented
acc << solution.tables.map(&:dup) acc << solution.tables.map(&:dup)
end end
acc acc

View File

@ -1,5 +1,7 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
require 'rails_helper' require 'rails_helper'
module Tables module Tables
@ -7,7 +9,7 @@ module Tables
describe '#each' do describe '#each' do
let(:swaps) do let(:swaps) do
acc = [] acc = []
described_class.new(initial_solution).each do |solution| described_class.new(initial_solution).each do |solution| # rubocop:disable Style/MapIntoArray -- #map is not implemented
acc << solution.tables.map(&:dup) acc << solution.tables.map(&:dup)
end end
acc acc

View File

@ -1,5 +1,7 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
require 'rails_helper' require 'rails_helper'
module VNS module VNS

View File

@ -1,5 +1,7 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
# This file was generated by the `rails generate rspec:install` command. Conventionally, all # This file was generated by the `rails generate rspec:install` command. Conventionally, all
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
# The generated `.rspec` file contains `--require spec_helper` which will cause # The generated `.rspec` file contains `--require spec_helper` which will cause
@ -46,51 +48,49 @@ RSpec.configure do |config|
# triggering implicit auto-inclusion in groups with matching metadata. # triggering implicit auto-inclusion in groups with matching metadata.
config.shared_context_metadata_behavior = :apply_to_host_groups config.shared_context_metadata_behavior = :apply_to_host_groups
# The settings below are suggested to provide a good initial experience # The settings below are suggested to provide a good initial experience
# with RSpec, but feel free to customize to your heart's content. # with RSpec, but feel free to customize to your heart's content.
=begin # # This allows you to limit a spec run to individual examples or groups
# This allows you to limit a spec run to individual examples or groups # # you care about by tagging them with `:focus` metadata. When nothing
# you care about by tagging them with `:focus` metadata. When nothing # # is tagged with `:focus`, all examples get run. RSpec also provides
# is tagged with `:focus`, all examples get run. RSpec also provides # # aliases for `it`, `describe`, and `context` that include `:focus`
# aliases for `it`, `describe`, and `context` that include `:focus` # # metadata: `fit`, `fdescribe` and `fcontext`, respectively.
# metadata: `fit`, `fdescribe` and `fcontext`, respectively. # config.filter_run_when_matching :focus
config.filter_run_when_matching :focus #
# # Allows RSpec to persist some state between runs in order to support
# Allows RSpec to persist some state between runs in order to support # # the `--only-failures` and `--next-failure` CLI options. We recommend
# the `--only-failures` and `--next-failure` CLI options. We recommend # # you configure your source control system to ignore this file.
# you configure your source control system to ignore this file. # config.example_status_persistence_file_path = "spec/examples.txt"
config.example_status_persistence_file_path = "spec/examples.txt" #
# # Limits the available syntax to the non-monkey patched syntax that is
# Limits the available syntax to the non-monkey patched syntax that is # # recommended. For more details, see:
# recommended. For more details, see: # # https://rspec.info/features/3-12/rspec-core/configuration/zero-monkey-patching-mode/
# https://rspec.info/features/3-12/rspec-core/configuration/zero-monkey-patching-mode/ # config.disable_monkey_patching!
config.disable_monkey_patching! #
# # Many RSpec users commonly either run the entire suite or an individual
# Many RSpec users commonly either run the entire suite or an individual # # file, and it's useful to allow more verbose output when running an
# file, and it's useful to allow more verbose output when running an # # individual spec file.
# individual spec file. # if config.files_to_run.one?
if config.files_to_run.one? # # Use the documentation formatter for detailed output,
# Use the documentation formatter for detailed output, # # unless a formatter has already been configured
# unless a formatter has already been configured # # (e.g. via a command-line flag).
# (e.g. via a command-line flag). # config.default_formatter = "doc"
config.default_formatter = "doc" # end
end #
# # Print the 10 slowest examples and example groups at the
# Print the 10 slowest examples and example groups at the # # end of the spec run, to help surface which specs are running
# end of the spec run, to help surface which specs are running # # particularly slow.
# particularly slow. # config.profile_examples = 10
config.profile_examples = 10 #
# # Run specs in random order to surface order dependencies. If you find an
# Run specs in random order to surface order dependencies. If you find an # # order dependency and want to debug it, you can fix the order by providing
# order dependency and want to debug it, you can fix the order by providing # # the seed, which is printed after each run.
# the seed, which is printed after each run. # # --seed 1234
# --seed 1234 # config.order = :random
config.order = :random #
# # Seed global randomization in this process using the `--seed` CLI option.
# Seed global randomization in this process using the `--seed` CLI option. # # Setting this allows you to use `--seed` to deterministically reproduce
# Setting this allows you to use `--seed` to deterministically reproduce # # test failures related to randomization by passing the same `--seed` value
# test failures related to randomization by passing the same `--seed` value # # as the one that triggered the failure.
# as the one that triggered the failure. # Kernel.srand config.seed
Kernel.srand config.seed
=end
end end

View File

@ -3,10 +3,10 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'rails_helper' require 'rails_helper'
require_relative './swagger_response_helper' require_relative 'swagger_response_helper'
require_relative './requests/schemas.rb' require_relative 'requests/schemas'
include SwaggerResponseHelper include SwaggerResponseHelper # rubocop:disable Style/MixinUsage
RSpec.configure do |config| RSpec.configure do |config|
# Specify a root folder where Swagger JSON files are generated # Specify a root folder where Swagger JSON files are generated

View File

@ -1,18 +1,19 @@
# Copyright (C) 2024 Manuel Bustillo # Copyright (C) 2024 Manuel Bustillo
# frozen_string_literal: true
module SwaggerResponseHelper module SwaggerResponseHelper
TIMESTAMP_FORMAT = '\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z' TIMESTAMP_FORMAT = '\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z'
TIMESTAMP_EXAMPLE = Time.zone.now.iso8601(3) TIMESTAMP_EXAMPLE = Time.zone.now.iso8601(3)
TIMESTAMP = {type: :string,pattern: TIMESTAMP_FORMAT,example: TIMESTAMP_EXAMPLE}.freeze TIMESTAMP = { type: :string, pattern: TIMESTAMP_FORMAT, example: TIMESTAMP_EXAMPLE }.freeze
PASSWORD = { type: :string, minLength: User.password_length.begin, maxLength: User.password_length.end } PASSWORD = { type: :string, minLength: User.password_length.begin, maxLength: User.password_length.end }.freeze
def regular_api_responses def regular_api_responses
response_401 response401
end end
def response_422 def response422
response(422, 'Validation errors in input parameters') do response(422, 'Validation errors in input parameters') do
produces 'application/json' produces 'application/json'
error_schema error_schema
@ -20,7 +21,7 @@ module SwaggerResponseHelper
end end
end end
def response_empty_200 def response_empty200
response(200, 'Success') do response(200, 'Success') do
produces 'application/json' produces 'application/json'
schema type: :object schema type: :object
@ -28,7 +29,7 @@ module SwaggerResponseHelper
end end
end end
def response_empty_201 def response_empty201
response(201, 'Created') do response(201, 'Created') do
produces 'application/json' produces 'application/json'
schema type: :object schema type: :object
@ -36,7 +37,7 @@ module SwaggerResponseHelper
end end
end end
def response_404 def response404
response(404, 'Record not found') do response(404, 'Record not found') do
produces 'application/json' produces 'application/json'
error_schema error_schema
@ -44,14 +45,14 @@ module SwaggerResponseHelper
end end
end end
def response_401(message: nil) def response401(message: nil)
response(401, 'Unauthorized') do response(401, 'Unauthorized') do
produces 'application/json' produces 'application/json'
schema type: :object, schema type: :object,
required: %i[error], required: %i[error],
properties: { properties: {
error: { type: :string, example: message || 'You need to sign in or sign up before continuing.' } error: { type: :string, example: message || 'You need to sign in or sign up before continuing.' }
} }
xit xit
end end
end end