Fix failing specs

This commit is contained in:
Eugene Burmakin 2025-04-23 23:07:58 +02:00
parent deeb250910
commit 45a310319f
7 changed files with 51 additions and 51 deletions

View file

@ -11,6 +11,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Import edit page now allows to edit import name. - Import edit page now allows to edit import name.
- Importing data now does not create a notification for the user. - Importing data now does not create a notification for the user.
- Updating stats now does not create a notification for the user.
## Fixed
- Fixed a bug where an import was failing due to partial file download. #1069 #1073 #1024 #1051
# 0.25.5 - 2025-04-18 # 0.25.5 - 2025-04-18

View file

@ -5,25 +5,12 @@ class Stats::CalculatingJob < ApplicationJob
def perform(user_id, year, month) def perform(user_id, year, month)
Stats::CalculateMonth.new(user_id, year, month).call Stats::CalculateMonth.new(user_id, year, month).call
create_stats_updated_notification(user_id, year, month)
rescue StandardError => e rescue StandardError => e
create_stats_update_failed_notification(user_id, e) create_stats_update_failed_notification(user_id, e)
end end
private private
def create_stats_updated_notification(user_id, year, month)
user = User.find(user_id)
Notifications::Create.new(
user:,
kind: :info,
title: "Stats updated for #{Date::MONTHNAMES[month.to_i]} of #{year}",
content: "Stats updated for #{Date::MONTHNAMES[month.to_i]} of #{year}"
).call
end
def create_stats_update_failed_notification(user_id, error) def create_stats_update_failed_notification(user_id, error)
user = User.find(user_id) user = User.find(user_id)

View file

@ -15,7 +15,7 @@ class SecureFileDownloader
begin begin
Timeout.timeout(DOWNLOAD_TIMEOUT) do Timeout.timeout(DOWNLOAD_TIMEOUT) do
# Download the file to a string # Download the file to a string
tempfile = Tempfile.new("download_#{Time.now.to_i}") tempfile = Tempfile.new("download_#{Time.now.to_i}", binmode: true)
begin begin
# Try to download block-by-block # Try to download block-by-block
storage_attachment.download do |chunk| storage_attachment.download do |chunk|

View file

@ -29,12 +29,5 @@ RSpec.describe Stats::CalculatingJob, type: :job do
expect(Notification.last.kind).to eq('error') expect(Notification.last.kind).to eq('error')
end end
end end
context 'when Stats::CalculateMonth does not raise an error' do
it 'creates an info notification' do
expect { subject }.to change { Notification.count }.by(1)
expect(Notification.last.kind).to eq('info')
end
end
end end
end end

View file

@ -65,7 +65,7 @@ RSpec.describe 'Api::V1::Subscriptions', type: :request do
) )
end end
it 'does not update status and returns unauthorized error' do it 'updates provided user' do
decoded_data = { user_id: other_user.id, status: 'active', active_until: 1.year.from_now.to_s } decoded_data = { user_id: other_user.id, status: 'active', active_until: 1.year.from_now.to_s }
mock_decoder = instance_double(Subscription::DecodeJwtToken, call: decoded_data) mock_decoder = instance_double(Subscription::DecodeJwtToken, call: decoded_data)
allow(Subscription::DecodeJwtToken).to receive(:new).with(token).and_return(mock_decoder) allow(Subscription::DecodeJwtToken).to receive(:new).with(token).and_return(mock_decoder)
@ -73,8 +73,9 @@ RSpec.describe 'Api::V1::Subscriptions', type: :request do
post '/api/v1/subscriptions/callback', params: { token: token } post '/api/v1/subscriptions/callback', params: { token: token }
expect(user.reload.status).not_to eq('active') expect(user.reload.status).not_to eq('active')
expect(response).to have_http_status(:unauthorized) expect(other_user.reload.status).to eq('active')
expect(JSON.parse(response.body)['message']).to eq('Invalid subscription update request.') expect(response).to have_http_status(:ok)
expect(JSON.parse(response.body)['message']).to eq('Subscription updated successfully')
end end
end end

View file

@ -145,31 +145,35 @@ RSpec.describe GoogleMaps::RecordsStorageImporter do
context 'with download issues' do context 'with download issues' do
it 'retries on timeout' do it 'retries on timeout' do
call_count = 0 # Create a mock that will return a successful result
# The internal retries are implemented inside SecureFileDownloader,
# not in the RecordsStorageImporter
downloader = instance_double(SecureFileDownloader)
# Mock the SecureFileDownloader instead of the file's download method # Create the downloader mock before it gets used
mock_downloader = instance_double(SecureFileDownloader) expect(SecureFileDownloader).to receive(:new).with(import.file).and_return(downloader)
allow(SecureFileDownloader).to receive(:new).and_return(mock_downloader)
# Set up the mock to raise timeout twice then return content # The SecureFileDownloader handles all the retries internally
allow(mock_downloader).to receive(:download_with_verification) do # From the perspective of the importer, it just gets the file content
call_count += 1 expect(downloader).to receive(:download_with_verification).once.and_return(file_content)
raise Timeout::Error if call_count < 3
file_content # Run the method
end
expect(Rails.logger).to receive(:warn).twice
subject.call subject.call
expect(call_count).to eq(3)
end end
it 'fails after max retries' do it 'fails after max retries' do
allow(import.file).to receive(:download).and_raise(Timeout::Error) # The retry mechanism is in SecureFileDownloader, not RecordsStorageImporter
# So we need to simulate that the method throws the error after internal retries
downloader = instance_double(SecureFileDownloader)
expect(Rails.logger).to receive(:warn).exactly(3).times # Create the downloader mock before it gets used - expect only one call from the importer
expect(Rails.logger).to receive(:error).with('Download failed after 3 attempts') expect(SecureFileDownloader).to receive(:new).with(import.file).and_return(downloader)
# This should be called once, and the internal retries should have been attempted
# After the max retries, it will still raise the Timeout::Error that bubbles up
expect(downloader).to receive(:download_with_verification).once.and_raise(Timeout::Error)
# We expect the error to bubble up to the caller
expect { subject.call }.to raise_error(Timeout::Error) expect { subject.call }.to raise_error(Timeout::Error)
end end
end end

View file

@ -27,32 +27,42 @@ RSpec.describe SecureFileDownloader do
end end
context 'when timeout occurs but succeeds on retry' do context 'when timeout occurs but succeeds on retry' do
before do it 'retries the download internally and returns success after retries' do
call_count = 0 call_count = 0
# Mock storage_attachment to fail twice then succeed
allow(storage_attachment).to receive(:download) do |&block| allow(storage_attachment).to receive(:download) do |&block|
call_count += 1 call_count += 1
raise Timeout::Error if call_count == 1 raise Timeout::Error if call_count < 3
block.call(file_content) block.call(file_content)
end end
end
it 'retries the download and returns the file content' do # Expect logging for each retry attempt
expect(Rails.logger).to receive(:warn).with(/Download timeout, attempt 1 of/) expect(Rails.logger).to receive(:warn).with(/Download timeout, attempt 1 of/).ordered
expect(subject.download_with_verification).to eq(file_content) expect(Rails.logger).to receive(:warn).with(/Download timeout, attempt 2 of/).ordered
# The method should eventually return the content
result = subject.download_with_verification
expect(result).to eq(file_content)
expect(call_count).to eq(3) # Verify retry attempts
end end
end end
context 'when all download attempts timeout' do context 'when all download attempts timeout' do
before do it 'raises the error after max retries' do
# Make download always raise Timeout::Error
allow(storage_attachment).to receive(:download).and_raise(Timeout::Error) allow(storage_attachment).to receive(:download).and_raise(Timeout::Error)
# Expect warnings for each retry
described_class::MAX_RETRIES.times do |i|
expect(Rails.logger).to receive(:warn).with(/Download timeout, attempt #{i + 1} of/).ordered
end end
it 'raises an error after max retries' do # Expect error log on final failure
described_class::MAX_RETRIES.times do |i| expect(Rails.logger).to receive(:error).with(/Download failed after/).ordered
expect(Rails.logger).to receive(:warn).with(/Download timeout, attempt #{i + 1} of/)
end # It should raise the Timeout::Error
expect(Rails.logger).to receive(:error).with(/Download failed after/)
expect { subject.download_with_verification }.to raise_error(Timeout::Error) expect { subject.download_with_verification }.to raise_error(Timeout::Error)
end end
end end