Files
sure/test/models/lunchflow_item/syncer_test.rb
T
Jeffandjeffrey701 12eb9e15fb fix(lunchflow): mark sync unhealthy when importer reports fetch failures (#1796) (#1873)
* fix(lunchflow): mark sync unhealthy when importer reports fetch failures (#1796)

`LunchflowItem::Syncer#perform_sync` called
`lunchflow_item.import_latest_lunchflow_data` but threw the result away
and then ran `collect_health_stats(sync, errors: nil)`. The importer
already catches per-account 429/500 fetch errors, bumps a
`transactions_failed` counter, and returns `success: false` — but the
syncer never inspected the return value, so the parent sync was marked
completed/green even when zero transactions were imported because every
fetch had been rate-limited.

Capture the importer result and translate any
`accounts_failed` / `transactions_failed` / `error` fields into the
`{ message:, category: }` error shape `collect_health_stats` expects.
The exception-path `rescue` branch is unchanged.

Closes #1796

* test(lunchflow): i18n the new health messages + add syncer invariant tests (#1796)

Two pieces of follow-up feedback:

- @coderabbitai + @JSONbored: the three new operator-facing strings should
  go through I18n.t. Add keys under provider_warnings.lunchflow_*
  (matching the existing provider_warnings.limited_investment_data
  shape) and use Rails pluralization for the count-bearing entries.
  Other locales follow the repo's normal translation flow.

- @jjmata + @JSONbored: add tests for the invariants. New
  LunchflowItem::SyncerTest covers:
    * successful import → sync healthy
    * accounts_failed positive → sync unhealthy with localized message
    * transactions_failed positive → sync unhealthy with localized message
    * both counters positive → both error entries recorded in order
    * sync raises → sync_error category + reraise (existing rescue branch)

@jjmata also asked to confirm the importer contract: LunchflowItem::Importer#import
returns 'success: accounts_failed == 0 && transactions_failed == 0' (see
importer.rb), so the early 'return [] if import_result[:success]' guard
is safe — success is never true while either counter is positive.

---------

Co-authored-by: jeffrey701 <jeffrey701@users.noreply.github.com>
2026-08-24 23:26:06 +02:00

116 lines
3.3 KiB
Ruby

# frozen_string_literal: true
require "test_helper"
class LunchflowItem::SyncerTest < ActiveSupport::TestCase
setup do
@lunchflow_item = lunchflow_items(:one)
@syncer = LunchflowItem::Syncer.new(@lunchflow_item)
end
test "marks sync healthy when import succeeds with no failures" do
sync = recording_sync
@lunchflow_item.expects(:import_latest_lunchflow_data).returns(
success: true,
accounts_failed: 0,
transactions_failed: 0
)
@syncer.perform_sync(sync)
assert_equal 0, sync.sync_stats["total_errors"]
assert_nil sync.sync_stats["errors"]
end
test "marks sync unhealthy when importer reports accounts_failed" do
sync = recording_sync
@lunchflow_item.expects(:import_latest_lunchflow_data).returns(
success: false,
accounts_failed: 2,
transactions_failed: 0
)
@syncer.perform_sync(sync)
assert_equal 1, sync.sync_stats["total_errors"]
assert_equal(
[ I18n.t("provider_warnings.lunchflow_accounts_failed", count: 2) ],
sync.sync_stats["errors"].map { |e| e["message"] }
)
assert_equal "lunchflow_import", sync.sync_stats["errors"].first["category"]
end
test "marks sync unhealthy when importer reports transactions_failed" do
sync = recording_sync
@lunchflow_item.expects(:import_latest_lunchflow_data).returns(
success: false,
accounts_failed: 0,
transactions_failed: 3
)
@syncer.perform_sync(sync)
assert_equal 1, sync.sync_stats["total_errors"]
assert_equal(
[ I18n.t("provider_warnings.lunchflow_transactions_failed", count: 3) ],
sync.sync_stats["errors"].map { |e| e["message"] }
)
end
test "records both failure categories when accounts and transactions both fail" do
sync = recording_sync
@lunchflow_item.expects(:import_latest_lunchflow_data).returns(
success: false,
accounts_failed: 1,
transactions_failed: 4
)
@syncer.perform_sync(sync)
assert_equal 2, sync.sync_stats["total_errors"]
assert_equal(
[
I18n.t("provider_warnings.lunchflow_accounts_failed", count: 1),
I18n.t("provider_warnings.lunchflow_transactions_failed", count: 4)
],
sync.sync_stats["errors"].map { |e| e["message"] }
)
end
test "captures sync_error category and reraises when import raises" do
sync = recording_sync
@lunchflow_item.expects(:import_latest_lunchflow_data).raises(StandardError, "boom")
assert_raises(StandardError) do
@syncer.perform_sync(sync)
end
assert_equal 1, sync.sync_stats["total_errors"]
error = sync.sync_stats["errors"].first
assert_equal "boom", error["message"]
assert_equal "sync_error", error["category"]
end
private
def recording_sync
Class.new do
attr_accessor :sync_stats, :status_text
attr_reader :updates, :window_start_date, :window_end_date
def initialize
@sync_stats = {}
@updates = []
@window_start_date = nil
@window_end_date = nil
end
def update!(attributes)
@updates << attributes
self.sync_stats = attributes[:sync_stats] if attributes.key?(:sync_stats)
self.status_text = attributes[:status_text] if attributes.key?(:status_text)
end
end.new
end
end