Skip to content

Commit 47a3b03

Browse files
committed
fix: correct bugs found during test coverage expansion
1 parent 3e6fe1f commit 47a3b03

File tree

13 files changed

+149
-93
lines changed

13 files changed

+149
-93
lines changed

app/controllers/api/v1/base_controller.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ module V1
3232
# end
3333
# end
3434
class BaseController < ApplicationController
35+
include ActionController::MimeResponds
3536
include Authenticatable
3637
include Pundit::Authorization
3738
include TrialChecker
@@ -94,7 +95,8 @@ def render_validation_errors(exception)
9495
end
9596

9697
def render_not_found(exception = nil)
97-
resource_name = exception&.model&.humanize || 'Resource'
98+
resource_name = exception.respond_to?(:model) ? exception.model&.humanize : nil
99+
resource_name ||= 'Resource'
98100
render_error(
99101
message: "#{resource_name} not found",
100102
code: 'NOT_FOUND',

app/controllers/api/v1/profile_controller.rb

Lines changed: 53 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,47 +21,83 @@ def update
2121
user: UserSerializer.render(@current_user)
2222
}, status: :ok
2323
else
24-
render json: { errors: @current_user.errors.full_messages }, status: :unprocessable_entity
24+
render_error(
25+
message: 'Validation failed',
26+
code: 'VALIDATION_ERROR',
27+
status: :unprocessable_entity,
28+
details: @current_user.errors.as_json
29+
)
2530
end
2631
end
2732

2833
# PATCH /api/v1/profile/password
2934
# Changes user password
3035
def update_password
31-
unless @current_user.authenticate(password_params[:current_password])
32-
return render json: { error: 'Current password is incorrect' }, status: :unauthorized
36+
unless @current_user.authenticate(params[:current_password])
37+
return render_error(
38+
message: 'Current password is incorrect',
39+
code: 'INVALID_PASSWORD',
40+
status: :unprocessable_entity
41+
)
3342
end
3443

35-
if @current_user.update(password: password_params[:new_password])
44+
new_password = params[:password]
45+
new_password_confirmation = params[:password_confirmation]
46+
47+
if new_password != new_password_confirmation
48+
return render_error(
49+
message: 'Password confirmation does not match',
50+
code: 'VALIDATION_ERROR',
51+
status: :unprocessable_entity
52+
)
53+
end
54+
55+
if @current_user.update(password: new_password)
3656
log_password_change
3757
render json: { message: 'Password updated successfully' }, status: :ok
3858
else
39-
render json: { errors: @current_user.errors.full_messages }, status: :unprocessable_entity
59+
render_error(
60+
message: 'Validation failed',
61+
code: 'VALIDATION_ERROR',
62+
status: :unprocessable_entity,
63+
details: @current_user.errors.as_json
64+
)
4065
end
4166
end
4267

4368
# PATCH /api/v1/profile/notifications
4469
# Updates notification preferences
4570
def update_notifications
46-
if @current_user.update(notification_params)
47-
render json: {
48-
message: 'Notification preferences updated successfully',
49-
notifications_enabled: @current_user.notifications_enabled,
50-
notification_preferences: @current_user.notification_preferences
51-
}, status: :ok
71+
prefs = params[:notification_preferences]
72+
73+
if prefs.nil?
74+
return render_error(
75+
message: 'Validation failed',
76+
code: 'VALIDATION_ERROR',
77+
status: :unprocessable_entity
78+
)
79+
end
80+
81+
notification_prefs = @current_user.notification_preferences.merge(prefs.to_unsafe_h.stringify_keys)
82+
83+
if @current_user.update(notification_preferences: notification_prefs)
84+
render_success({
85+
notification_preferences: @current_user.notification_preferences
86+
}, message: 'Notification preferences updated successfully')
5287
else
53-
render json: { errors: @current_user.errors.full_messages }, status: :unprocessable_entity
88+
render_error(
89+
message: 'Validation failed',
90+
code: 'VALIDATION_ERROR',
91+
status: :unprocessable_entity,
92+
details: @current_user.errors.as_json
93+
)
5494
end
5595
end
5696

5797
private
5898

5999
def profile_params
60-
params.require(:user).permit(:full_name, :avatar_url, :timezone, :language)
61-
end
62-
63-
def password_params
64-
params.require(:user).permit(:current_password, :new_password)
100+
params.require(:user).permit(:full_name, :email, :avatar_url, :timezone, :language)
65101
end
66102

67103
def notification_params

app/modules/analytics/controllers/objectives_controller.rb

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,7 @@ def build_objective_score(matches, total)
141141
end
142142

143143
def build_objective_trend(matches)
144-
matches.includes(:match)
145-
.order('game_start ASC')
144+
matches.order('game_start ASC')
146145
.last(30)
147146
.map do |m|
148147
next unless m.game_start

app/modules/analytics/controllers/teamfights_controller.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@ def show
2020
player = organization_scoped(Player).find(params[:player_id])
2121

2222
stats = PlayerMatchStat.joins(:match)
23-
.includes(:match)
24-
.where(player: player, match: { organization: current_organization })
23+
.where(player: player)
24+
.where('matches.organization_id = ?', current_organization.id)
2525
.order('matches.game_start DESC')
26+
.preload(:match)
2627
.limit(20)
2728

2829
teamfight_data = {

app/modules/competitive/services/draft_comparator_service.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def self.compare_draft(our_picks:, opponent_picks:, organization:, our_bans: [],
3434
our_picks: our_picks,
3535
opponent_picks: opponent_picks,
3636
our_bans: our_bans,
37-
opponent_bans: opponent_bans,
37+
_opponent_bans: opponent_bans,
3838
patch: patch,
3939
organization: organization
4040
)
@@ -60,7 +60,7 @@ def compare_draft(our_picks:, opponent_picks:, our_bans:, _opponent_bans:, patch
6060

6161
# Generate insights
6262
insights = analyzer.generate_insights(
63-
our_picks: our_picks,
63+
_our_picks: our_picks,
6464
opponent_picks: opponent_picks,
6565
our_bans: our_bans,
6666
similar_matches: similar_matches,

app/modules/matches/controllers/matches_controller.rb

Lines changed: 16 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ def stats
120120
end,
121121
comparison: {
122122
total_gold: stats.sum(:gold_earned),
123-
total_damage: stats.sum(:total_damage_dealt),
123+
total_damage: stats.sum(:damage_dealt_total),
124124
total_vision_score: stats.sum(:vision_score),
125125
avg_kda: calculate_avg_kda(stats)
126126
}
@@ -138,63 +138,22 @@ def import
138138
unless player.riot_puuid.present?
139139
return render_error(
140140
message: 'Player does not have a Riot PUUID. Please sync player from Riot first.',
141-
code: 'VALIDATION_ERROR',
142-
status: :unprocessable_entity
141+
code: 'MISSING_PUUID',
142+
status: :bad_request
143143
)
144144
end
145145

146-
begin
147-
riot_service = RiotApiService.new
148-
region = player.region || 'BR'
149-
150-
match_ids = riot_service.get_match_history(
151-
puuid: player.riot_puuid,
152-
region: region,
153-
count: count
154-
)
155-
156-
imported_count = 0
157-
match_ids.each do |match_id|
158-
next if Match.exists?(riot_match_id: match_id)
146+
job_id = ImportPlayerMatchesJob.perform_later(
147+
player.id,
148+
current_organization.id,
149+
count
150+
).job_id
159151

160-
SyncMatchJob.perform_later(match_id, current_organization.id, region)
161-
imported_count += 1
162-
end
163-
164-
render_success({
165-
message: "Queued #{imported_count} matches for import",
166-
total_matches_found: match_ids.count,
167-
already_imported: match_ids.count - imported_count,
168-
player: PlayerSerializer.render_as_hash(player)
169-
})
170-
rescue RedisClient::CannotConnectError, Redis::CannotConnectError => e
171-
Rails.logger.error "Redis connection failed during match import: #{e.message}"
172-
173-
render_error(
174-
message: 'Background job service is temporarily unavailable. Please try again later.',
175-
code: 'BACKGROUND_SERVICE_UNAVAILABLE',
176-
status: :service_unavailable,
177-
details: {
178-
hint: 'The import service is currently down. Contact your administrator if this persists.',
179-
player_id: player.id
180-
}
181-
)
182-
rescue RiotApiService::RiotApiError => e
183-
render_error(
184-
message: "Failed to fetch matches from Riot API: #{e.message}",
185-
code: 'RIOT_API_ERROR',
186-
status: :bad_gateway
187-
)
188-
rescue StandardError => e
189-
Rails.logger.error "Unexpected error during match import: #{e.class} - #{e.message}"
190-
Rails.logger.error e.backtrace.first(5).join("\n")
191-
192-
render_error(
193-
message: "Failed to import matches: #{e.message}",
194-
code: 'IMPORT_ERROR',
195-
status: :internal_server_error
196-
)
197-
end
152+
render_success({
153+
job_id: job_id,
154+
player_id: player.id.to_s,
155+
count: count
156+
}, message: 'Match import queued successfully')
198157
end
199158

200159
private
@@ -265,7 +224,7 @@ def calculate_matches_summary(matches)
265224
victories: matches.victories.count,
266225
defeats: matches.defeats.count,
267226
win_rate: calculate_win_rate(matches),
268-
by_type: matches.group(:match_type).count,
227+
by_type: matches.unscope(:order).group(:match_type).count,
269228
avg_duration: matches.average(:game_duration)&.round(0)
270229
}
271230
end
@@ -276,8 +235,8 @@ def calculate_team_stats(stats)
276235
total_deaths: stats.sum(:deaths),
277236
total_assists: stats.sum(:assists),
278237
total_gold: stats.sum(:gold_earned),
279-
total_damage: stats.sum(:total_damage_dealt),
280-
total_cs: stats.sum(:minions_killed),
238+
total_damage: stats.sum(:damage_dealt_total),
239+
total_cs: stats.sum(:cs),
281240
total_vision_score: stats.sum(:vision_score)
282241
}
283242
end
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# frozen_string_literal: true
2+
3+
module Matches
4+
# Background job that fetches match history from Riot API for a specific player
5+
# and queues individual SyncMatchJob jobs for each match to import.
6+
class ImportPlayerMatchesJob < ApplicationJob
7+
queue_as :default
8+
9+
def perform(player_id, organization_id, count = 20)
10+
Current.organization_id = organization_id
11+
12+
organization = Organization.find(organization_id)
13+
player = organization.players.find(player_id)
14+
15+
return unless player.riot_puuid.present?
16+
17+
riot_service = RiotApiService.new
18+
region = player.region || 'BR'
19+
20+
match_ids = riot_service.get_match_history(
21+
puuid: player.riot_puuid,
22+
region: region,
23+
count: count
24+
)
25+
26+
match_ids.each do |match_id|
27+
next if Match.exists?(riot_match_id: match_id)
28+
29+
SyncMatchJob.perform_later(match_id, organization_id, region)
30+
end
31+
rescue RiotApiService::RiotApiError => e
32+
Rails.logger.error("ImportPlayerMatchesJob: Riot API error for player #{player_id}: #{e.message}")
33+
rescue StandardError => e
34+
Rails.logger.error("ImportPlayerMatchesJob: Unexpected error for player #{player_id}: #{e.class} - #{e.message}")
35+
raise
36+
ensure
37+
Current.organization_id = nil
38+
end
39+
end
40+
end

app/modules/notifications/controllers/notifications_controller.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,15 +65,15 @@ def destroy
6565

6666
render_deleted(message: 'Notification deleted successfully')
6767
rescue ActiveRecord::RecordNotFound
68-
render_not_found('Notification not found')
68+
render_not_found
6969
end
7070

7171
private
7272

7373
def set_notification
7474
@notification = current_user.notifications.find(params[:id])
7575
rescue ActiveRecord::RecordNotFound
76-
render_not_found('Notification not found')
76+
render_not_found
7777
end
7878
end
7979
end

app/modules/scouting/controllers/players_controller.rb

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,8 @@ def create
7777
# Updates global target data OR watchlist data
7878
def update
7979
ActiveRecord::Base.transaction do
80-
@target.update!(target_params) if target_params.any?
80+
tp = target_params.to_h
81+
@target.update!(tp) if tp.any?
8182
update_watchlist_if_params_present
8283
render_updated(serialized_target_response)
8384
end
@@ -134,13 +135,15 @@ def create_watchlist_for(target)
134135
end
135136

136137
def update_watchlist_if_params_present
137-
return unless watchlist_params.any?
138+
wp = watchlist_params.to_h
139+
wp = scouting_target_watchlist_params.to_h if wp.empty?
140+
return if wp.empty?
138141

139142
watchlist = @target.scouting_watchlists.find_or_create_by!(organization: current_organization) do |w|
140143
w.added_by = current_user
141144
end
142145
old_values = watchlist.attributes.dup
143-
watchlist.update!(watchlist_params)
146+
watchlist.update!(wp)
144147
log_user_action(action: 'update', entity_type: 'ScoutingWatchlist',
145148
entity_id: watchlist.id, old_values: old_values, new_values: watchlist.attributes)
146149
end
@@ -306,6 +309,12 @@ def watchlist_params
306309
)
307310
end
308311

312+
def scouting_target_watchlist_params
313+
params.fetch(:scouting_target, {}).permit(
314+
:priority, :status, :notes, :assigned_to_id
315+
)
316+
end
317+
309318
def target_params
310319
# :role is the LoL in-game position (top/jungle/mid/adc/support), not an authorization role.
311320
params.fetch(:target, {}).permit( # nosemgrep: ruby.lang.security.model-attr-accessible.model-attr-accessible

app/modules/scouting/controllers/regions_controller.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def index
3333
{ code: 'TR', name: 'Turkey', platform: 'TR1' }
3434
]
3535

36-
render_success(regions)
36+
render_success({ regions: regions })
3737
end
3838
end
3939
end

0 commit comments

Comments
 (0)