mirror of
https://github.com/we-promise/sure.git
synced 2026-05-08 21:25:00 +00:00
Add tests for TwelveData rate limit error handling
- Test rate limit error detection in Provider::TwelveData - Test error propagation in Security::Price::Importer - Test job retry configuration in SyncJob Co-authored-by: jjmata <187772+jjmata@users.noreply.github.com>
This commit is contained in:
@@ -10,4 +10,24 @@ class SyncJobTest < ActiveJob::TestCase
|
||||
|
||||
SyncJob.perform_now(sync)
|
||||
end
|
||||
|
||||
test "retries on TwelveData rate limit error" do
|
||||
syncable = accounts(:depository)
|
||||
sync = syncable.syncs.create!(window_start_date: 2.days.ago.to_date)
|
||||
|
||||
# Create a rate limit error
|
||||
rate_limit_error = Provider::TwelveData::RateLimitError.new(
|
||||
"TwelveData rate limit exceeded",
|
||||
details: { code: 429 }
|
||||
)
|
||||
|
||||
# Mock sync.perform to raise the rate limit error
|
||||
sync.stubs(:perform).raises(rate_limit_error)
|
||||
|
||||
# Verify the job is configured to retry on this error
|
||||
assert_raises(Provider::TwelveData::RateLimitError) do
|
||||
SyncJob.perform_now(sync)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
125
test/models/provider/twelve_data_test.rb
Normal file
125
test/models/provider/twelve_data_test.rb
Normal file
@@ -0,0 +1,125 @@
|
||||
require "test_helper"
|
||||
|
||||
class Provider::TwelveDataTest < ActiveSupport::TestCase
|
||||
setup do
|
||||
@provider = Provider::TwelveData.new("test_api_key")
|
||||
end
|
||||
|
||||
# ================================
|
||||
# Rate Limit Error Tests
|
||||
# ================================
|
||||
|
||||
test "raises RateLimitError on 429 response" do
|
||||
# Mock a 429 rate limit response from the API
|
||||
error_body = JSON.generate({
|
||||
"code" => 429,
|
||||
"message" => "You have run out of API credits for the current minute. 27 API credits were used, with the current limit being 8.",
|
||||
"status" => "error"
|
||||
})
|
||||
|
||||
faraday_error = Faraday::ClientError.new("the server responded with status 429")
|
||||
faraday_error.instance_variable_set(:@response, {
|
||||
status: 429,
|
||||
body: error_body
|
||||
})
|
||||
|
||||
@provider.stubs(:client).returns(mock_client = mock)
|
||||
mock_client.stubs(:get).raises(faraday_error)
|
||||
|
||||
response = @provider.fetch_security_prices(
|
||||
symbol: "AAPL",
|
||||
exchange_operating_mic: "XNAS",
|
||||
start_date: Date.parse("2024-01-01"),
|
||||
end_date: Date.parse("2024-01-10")
|
||||
)
|
||||
|
||||
assert_not response.success?
|
||||
assert_instance_of Provider::TwelveData::RateLimitError, response.error
|
||||
assert_match(/rate limit exceeded/i, response.error.message)
|
||||
end
|
||||
|
||||
test "raises RateLimitError for exchange rates on 429 response" do
|
||||
# Mock a 429 rate limit response
|
||||
error_body = JSON.generate({
|
||||
"code" => 429,
|
||||
"message" => "Rate limit exceeded",
|
||||
"status" => "error"
|
||||
})
|
||||
|
||||
faraday_error = Faraday::ClientError.new("the server responded with status 429")
|
||||
faraday_error.instance_variable_set(:@response, {
|
||||
status: 429,
|
||||
body: error_body
|
||||
})
|
||||
|
||||
@provider.stubs(:client).returns(mock_client = mock)
|
||||
mock_client.stubs(:get).raises(faraday_error)
|
||||
|
||||
response = @provider.fetch_exchange_rates(
|
||||
from: "USD",
|
||||
to: "EUR",
|
||||
start_date: Date.parse("2024-01-01"),
|
||||
end_date: Date.parse("2024-01-10")
|
||||
)
|
||||
|
||||
assert_not response.success?
|
||||
assert_instance_of Provider::TwelveData::RateLimitError, response.error
|
||||
end
|
||||
|
||||
test "handles non-rate-limit errors normally" do
|
||||
# Mock a 500 server error
|
||||
error_body = JSON.generate({
|
||||
"code" => 500,
|
||||
"message" => "Internal server error",
|
||||
"status" => "error"
|
||||
})
|
||||
|
||||
faraday_error = Faraday::ServerError.new("the server responded with status 500")
|
||||
faraday_error.instance_variable_set(:@response, {
|
||||
status: 500,
|
||||
body: error_body
|
||||
})
|
||||
|
||||
@provider.stubs(:client).returns(mock_client = mock)
|
||||
mock_client.stubs(:get).raises(faraday_error)
|
||||
|
||||
response = @provider.fetch_security_prices(
|
||||
symbol: "AAPL",
|
||||
exchange_operating_mic: "XNAS",
|
||||
start_date: Date.parse("2024-01-01"),
|
||||
end_date: Date.parse("2024-01-10")
|
||||
)
|
||||
|
||||
assert_not response.success?
|
||||
# Should be a regular error, not a RateLimitError
|
||||
assert_instance_of Provider::TwelveData::Error, response.error
|
||||
assert_not_instance_of Provider::TwelveData::RateLimitError, response.error
|
||||
end
|
||||
|
||||
test "extracts error message from JSON response body" do
|
||||
error_body = JSON.generate({
|
||||
"code" => 429,
|
||||
"message" => "Custom rate limit message",
|
||||
"status" => "error"
|
||||
})
|
||||
|
||||
faraday_error = Faraday::ClientError.new("the server responded with status 429")
|
||||
faraday_error.instance_variable_set(:@response, {
|
||||
status: 429,
|
||||
body: error_body
|
||||
})
|
||||
|
||||
@provider.stubs(:client).returns(mock_client = mock)
|
||||
mock_client.stubs(:get).raises(faraday_error)
|
||||
|
||||
response = @provider.fetch_security_prices(
|
||||
symbol: "AAPL",
|
||||
exchange_operating_mic: "XNAS",
|
||||
start_date: Date.parse("2024-01-01"),
|
||||
end_date: Date.parse("2024-01-10")
|
||||
)
|
||||
|
||||
assert_not response.success?
|
||||
assert_match(/Custom rate limit message/, response.error.message)
|
||||
end
|
||||
end
|
||||
@@ -412,6 +412,69 @@ class Security::Price::ImporterTest < ActiveSupport::TestCase
|
||||
end
|
||||
end
|
||||
|
||||
test "re-raises rate limit errors from TwelveData provider" do
|
||||
Security::Price.delete_all
|
||||
|
||||
# Create a rate limit error wrapped in a failed response
|
||||
rate_limit_error = Provider::TwelveData::RateLimitError.new(
|
||||
"TwelveData rate limit exceeded: You have run out of API credits for the current minute.",
|
||||
details: { code: 429 }
|
||||
)
|
||||
provider_response = Provider::Response.new(
|
||||
success?: false,
|
||||
data: nil,
|
||||
error: rate_limit_error
|
||||
)
|
||||
|
||||
@provider.expects(:fetch_security_prices)
|
||||
.with(symbol: @security.ticker, exchange_operating_mic: @security.exchange_operating_mic,
|
||||
start_date: get_provider_fetch_start_date(2.days.ago.to_date), end_date: Date.current)
|
||||
.returns(provider_response)
|
||||
|
||||
importer = Security::Price::Importer.new(
|
||||
security: @security,
|
||||
security_provider: @provider,
|
||||
start_date: 2.days.ago.to_date,
|
||||
end_date: Date.current
|
||||
)
|
||||
|
||||
# The rate limit error should be re-raised so the job can retry
|
||||
assert_raises(Provider::TwelveData::RateLimitError) do
|
||||
importer.import_provider_prices
|
||||
end
|
||||
end
|
||||
|
||||
test "does not re-raise non-rate-limit errors from provider" do
|
||||
Security::Price.delete_all
|
||||
|
||||
# Create a regular error (not rate limit)
|
||||
regular_error = Provider::TwelveData::Error.new("API error", details: { code: 500 })
|
||||
provider_response = Provider::Response.new(
|
||||
success?: false,
|
||||
data: nil,
|
||||
error: regular_error
|
||||
)
|
||||
|
||||
@provider.expects(:fetch_security_prices)
|
||||
.with(symbol: @security.ticker, exchange_operating_mic: @security.exchange_operating_mic,
|
||||
start_date: get_provider_fetch_start_date(2.days.ago.to_date), end_date: Date.current)
|
||||
.returns(provider_response)
|
||||
|
||||
importer = Security::Price::Importer.new(
|
||||
security: @security,
|
||||
security_provider: @provider,
|
||||
start_date: 2.days.ago.to_date,
|
||||
end_date: Date.current
|
||||
)
|
||||
|
||||
# Regular errors should not be re-raised, just logged
|
||||
# The method returns 0 (no prices imported)
|
||||
assert_nothing_raised do
|
||||
result = importer.import_provider_prices
|
||||
assert_equal 0, result
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def get_provider_fetch_start_date(start_date)
|
||||
start_date - Security::Price::Importer::PROVISIONAL_LOOKBACK_DAYS.days
|
||||
|
||||
Reference in New Issue
Block a user