1
0
Fork 0
mirror of https://github.com/maybe-finance/maybe.git synced 2025-07-18 20:59:39 +02:00
Maybe/test/models/security/health_checker_test.rb
Zach Gollwitzer e4ee06c9f6
Security resolver and health checker (#2281)
* Setup health check

* Security health checker cron

* Use resolver throughout codebase

* Use resolver for trade builder

* Add security health checks to schedule

* Handle no provider

* Lint fixes
2025-05-22 12:43:24 -04:00

158 lines
4.9 KiB
Ruby

require "test_helper"
class Security::HealthCheckerTest < ActiveSupport::TestCase
include ProviderTestHelper
setup do
# Clean slate
Holding.destroy_all
Trade.destroy_all
Security::Price.delete_all
Security.delete_all
@provider = mock
Security.stubs(:provider).returns(@provider)
# Brand new, no health check has been run yet
@new_security = Security.create!(
ticker: "NEW",
offline: false,
last_health_check_at: nil
)
# New security, offline
# This will be checked, but unless it gets a price, we keep it offline
@new_offline_security = Security.create!(
ticker: "NEW_OFFLINE",
offline: true,
last_health_check_at: nil
)
# Online, recently checked, healthy
@healthy_security = Security.create!(
ticker: "HEALTHY",
offline: false,
last_health_check_at: 2.hours.ago
)
# Online, due for a health check
@due_for_check_security = Security.create!(
ticker: "DUE",
offline: false,
last_health_check_at: Security::HealthChecker::HEALTH_CHECK_INTERVAL.ago - 1.day
)
# Offline, recently checked (keep offline, don't check)
@offline_security = Security.create!(
ticker: "OFFLINE",
offline: true,
last_health_check_at: 20.days.ago
)
# Currently offline, but has had no health check and actually has prices (needs to convert to "online")
@offline_never_checked_with_prices = Security.create!(
ticker: "OFFLINE_NEVER_CHECKED",
offline: true,
last_health_check_at: nil
)
end
test "any security without a health check runs" do
to_check = Security.where(last_health_check_at: nil).or(Security.where(last_health_check_at: ..Security::HealthChecker::HEALTH_CHECK_INTERVAL.ago))
Security::HealthChecker.any_instance.expects(:run_check).times(to_check.count)
Security::HealthChecker.check_all
end
test "offline security with no health check that fails stays offline" do
hc = Security::HealthChecker.new(@new_offline_security)
@provider.expects(:fetch_security_price)
.with(
symbol: @new_offline_security.ticker,
exchange_operating_mic: @new_offline_security.exchange_operating_mic,
date: Date.current
)
.returns(
provider_error_response(StandardError.new("No prices found"))
)
.once
hc.run_check
assert_equal 1, @new_offline_security.failed_fetch_count
assert @new_offline_security.offline?
end
test "after enough consecutive health check failures, security goes offline and prices are deleted" do
# Create one test price
Security::Price.create!(
security: @due_for_check_security,
date: Date.current,
price: 100,
currency: "USD"
)
hc = Security::HealthChecker.new(@due_for_check_security)
@provider.expects(:fetch_security_price)
.with(
symbol: @due_for_check_security.ticker,
exchange_operating_mic: @due_for_check_security.exchange_operating_mic,
date: Date.current
)
.returns(provider_error_response(StandardError.new("No prices found")))
.times(Security::HealthChecker::MAX_CONSECUTIVE_FAILURES + 1)
Security::HealthChecker::MAX_CONSECUTIVE_FAILURES.times do
hc.run_check
end
refute @due_for_check_security.offline?
assert_equal 1, @due_for_check_security.prices.count
# We've now exceeded the max consecutive failures, so the security should be marked offline
hc.run_check
assert @due_for_check_security.offline?
assert_equal 0, @due_for_check_security.prices.count
end
test "failure incrementor increases for each health check failure" do
hc = Security::HealthChecker.new(@due_for_check_security)
@provider.expects(:fetch_security_price)
.with(
symbol: @due_for_check_security.ticker,
exchange_operating_mic: @due_for_check_security.exchange_operating_mic,
date: Date.current
)
.returns(provider_error_response(StandardError.new("No prices found")))
.twice
hc.run_check
assert_equal 1, @due_for_check_security.failed_fetch_count
hc.run_check
assert_equal 2, @due_for_check_security.failed_fetch_count
end
test "failure incrementor resets to 0 when health check succeeds" do
hc = Security::HealthChecker.new(@offline_never_checked_with_prices)
@provider.expects(:fetch_security_price)
.with(
symbol: @offline_never_checked_with_prices.ticker,
exchange_operating_mic: @offline_never_checked_with_prices.exchange_operating_mic,
date: Date.current
)
.returns(provider_success_response(OpenStruct.new(price: 100, date: Date.current, currency: "USD")))
.once
assert @offline_never_checked_with_prices.offline?
hc.run_check
refute @offline_never_checked_with_prices.offline?
assert_equal 0, @offline_never_checked_with_prices.failed_fetch_count
assert_nil @offline_never_checked_with_prices.failed_fetch_at
end
end