mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-08-10 16:05:22 +02:00
Move sync orchestration to Sync class
This commit is contained in:
parent
6ddb4d088e
commit
5432b3903d
6 changed files with 91 additions and 82 deletions
|
@ -5,19 +5,23 @@ class Account::Syncer
|
||||||
@account = account
|
@account = account
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def child_syncables
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
|
||||||
def perform_sync(start_date: nil)
|
def perform_sync(start_date: nil)
|
||||||
Rails.logger.info("Processing balances (#{account.linked? ? 'reverse' : 'forward'})")
|
Rails.logger.info("Processing balances (#{account.linked? ? 'reverse' : 'forward'})")
|
||||||
sync_balances
|
sync_balances
|
||||||
end
|
end
|
||||||
|
|
||||||
def perform_post_sync(sync)
|
def perform_post_sync
|
||||||
account.family.remove_syncing_notice!
|
account.family.remove_syncing_notice!
|
||||||
|
|
||||||
account.accountable.post_sync(sync)
|
# account.accountable.post_sync(sync)
|
||||||
|
|
||||||
unless sync.child?
|
# unless sync.child?
|
||||||
account.family.auto_match_transfers!
|
account.family.auto_match_transfers!
|
||||||
end
|
# end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -5,27 +5,21 @@ class Family::Syncer
|
||||||
@family = family
|
@family = family
|
||||||
end
|
end
|
||||||
|
|
||||||
def perform_sync(sync, start_date: nil)
|
def child_syncables
|
||||||
|
family.plaid_items + family.accounts.manual
|
||||||
|
end
|
||||||
|
|
||||||
|
def perform_sync(start_date: nil)
|
||||||
# We don't rely on this value to guard the app, but keep it eventually consistent
|
# We don't rely on this value to guard the app, but keep it eventually consistent
|
||||||
family.sync_trial_status!
|
family.sync_trial_status!
|
||||||
|
|
||||||
Rails.logger.info("Syncing accounts for family #{family.id}")
|
|
||||||
family.accounts.manual.each do |account|
|
|
||||||
account.sync_later(start_date: start_date, parent_sync: sync)
|
|
||||||
end
|
|
||||||
|
|
||||||
Rails.logger.info("Syncing plaid items for family #{family.id}")
|
|
||||||
family.plaid_items.each do |plaid_item|
|
|
||||||
plaid_item.sync_later(start_date: start_date, parent_sync: sync)
|
|
||||||
end
|
|
||||||
|
|
||||||
Rails.logger.info("Applying rules for family #{family.id}")
|
Rails.logger.info("Applying rules for family #{family.id}")
|
||||||
family.rules.each do |rule|
|
family.rules.each do |rule|
|
||||||
rule.apply_later
|
rule.apply_later
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def perform_post_sync(sync)
|
def perform_post_sync
|
||||||
family.auto_match_transfers!
|
family.auto_match_transfers!
|
||||||
family.broadcast_refresh
|
family.broadcast_refresh
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,17 +5,16 @@ class PlaidItem::Syncer
|
||||||
@plaid_item = plaid_item
|
@plaid_item = plaid_item
|
||||||
end
|
end
|
||||||
|
|
||||||
def perform_sync(sync, start_date: nil)
|
def child_syncables
|
||||||
|
plaid_item.accounts
|
||||||
|
end
|
||||||
|
|
||||||
|
def perform_sync(start_date: nil)
|
||||||
begin
|
begin
|
||||||
Rails.logger.info("Fetching and loading Plaid data")
|
Rails.logger.info("Fetching and loading Plaid data")
|
||||||
fetch_and_load_plaid_data
|
fetch_and_load_plaid_data
|
||||||
plaid_item.update!(status: :good) if plaid_item.requires_update?
|
plaid_item.update!(status: :good) if plaid_item.requires_update?
|
||||||
|
|
||||||
# Schedule account syncs
|
|
||||||
plaid_item.accounts.each do |account|
|
|
||||||
account.sync_later(start_date: start_date, parent_sync: sync)
|
|
||||||
end
|
|
||||||
|
|
||||||
Rails.logger.info("Plaid data fetched and loaded")
|
Rails.logger.info("Plaid data fetched and loaded")
|
||||||
rescue Plaid::ApiError => e
|
rescue Plaid::ApiError => e
|
||||||
handle_plaid_error(e)
|
handle_plaid_error(e)
|
||||||
|
@ -23,7 +22,7 @@ class PlaidItem::Syncer
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def perform_post_sync(sync)
|
def perform_post_sync
|
||||||
plaid_item.auto_match_categories!
|
plaid_item.auto_match_categories!
|
||||||
plaid_item.family.broadcast_refresh
|
plaid_item.family.broadcast_refresh
|
||||||
end
|
end
|
||||||
|
|
|
@ -19,12 +19,17 @@ class Sync < ApplicationRecord
|
||||||
start!
|
start!
|
||||||
|
|
||||||
begin
|
begin
|
||||||
syncer.perform_sync(self, start_date: start_date)
|
syncer.perform_sync(start_date: start_date)
|
||||||
|
|
||||||
|
# Schedule child syncables to sync later
|
||||||
|
syncer.child_syncables.each do |child_syncable|
|
||||||
|
child_syncable.sync_later(start_date: start_date, parent_sync: self)
|
||||||
|
end
|
||||||
|
|
||||||
unless has_pending_child_syncs?
|
unless has_pending_child_syncs?
|
||||||
complete!
|
complete!
|
||||||
Rails.logger.info("Sync completed, starting post-sync")
|
Rails.logger.info("Sync completed, starting post-sync")
|
||||||
syncer.perform_post_sync(self)
|
syncer.perform_post_sync
|
||||||
Rails.logger.info("Post-sync completed")
|
Rails.logger.info("Post-sync completed")
|
||||||
end
|
end
|
||||||
rescue StandardError => error
|
rescue StandardError => error
|
||||||
|
@ -51,7 +56,7 @@ class Sync < ApplicationRecord
|
||||||
# If this sync is both a child and a parent, we need to notify the parent of completion
|
# If this sync is both a child and a parent, we need to notify the parent of completion
|
||||||
notify_parent_of_completion! if has_parent?
|
notify_parent_of_completion! if has_parent?
|
||||||
|
|
||||||
syncer.perform_post_sync(self)
|
syncer.perform_post_sync
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -11,14 +11,9 @@ class Family::SyncerTest < ActiveSupport::TestCase
|
||||||
manual_accounts_count = @family.accounts.manual.count
|
manual_accounts_count = @family.accounts.manual.count
|
||||||
items_count = @family.plaid_items.count
|
items_count = @family.plaid_items.count
|
||||||
|
|
||||||
Account.any_instance.expects(:sync_later)
|
syncer = Family::Syncer.new(@family)
|
||||||
.with(start_date: nil, parent_sync: family_sync)
|
syncer.perform_sync(start_date: family_sync.start_date)
|
||||||
.times(manual_accounts_count)
|
|
||||||
|
|
||||||
PlaidItem.any_instance.expects(:sync_later)
|
assert_equal manual_accounts_count + items_count, syncer.child_syncables.count
|
||||||
.with(start_date: nil, parent_sync: family_sync)
|
|
||||||
.times(items_count)
|
|
||||||
|
|
||||||
Family::Syncer.new(@family).perform_sync(family_sync, start_date: family_sync.start_date)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,74 +1,86 @@
|
||||||
require "test_helper"
|
require "test_helper"
|
||||||
|
|
||||||
class SyncTest < ActiveSupport::TestCase
|
class SyncTest < ActiveSupport::TestCase
|
||||||
setup do
|
include ActiveJob::TestHelper
|
||||||
@sync = syncs(:account)
|
|
||||||
@sync.update(status: "pending")
|
|
||||||
end
|
|
||||||
|
|
||||||
test "runs successful sync" do
|
test "runs successful sync" do
|
||||||
Account::Syncer.any_instance.expects(:perform_sync).with(@sync, start_date: @sync.start_date).once
|
sync = Sync.create!(syncable: accounts(:depository), last_ran_at: 1.day.ago)
|
||||||
|
|
||||||
assert_equal "pending", @sync.status
|
Account::Syncer.any_instance.expects(:perform_sync).with(start_date: sync.start_date).once
|
||||||
|
|
||||||
previously_ran_at = @sync.last_ran_at
|
assert_equal "pending", sync.status
|
||||||
|
|
||||||
@sync.perform
|
previously_ran_at = sync.last_ran_at
|
||||||
|
|
||||||
assert @sync.last_ran_at > previously_ran_at
|
sync.perform
|
||||||
assert_equal "completed", @sync.status
|
|
||||||
|
assert sync.last_ran_at > previously_ran_at
|
||||||
|
assert_equal "completed", sync.status
|
||||||
end
|
end
|
||||||
|
|
||||||
test "handles sync errors" do
|
test "handles sync errors" do
|
||||||
Account::Syncer.any_instance.expects(:perform_sync).with(@sync, start_date: @sync.start_date).raises(StandardError.new("test sync error"))
|
sync = Sync.create!(syncable: accounts(:depository), last_ran_at: 1.day.ago)
|
||||||
|
Account::Syncer.any_instance.expects(:perform_sync).with(start_date: sync.start_date).raises(StandardError.new("test sync error"))
|
||||||
|
|
||||||
assert_equal "pending", @sync.status
|
assert_equal "pending", sync.status
|
||||||
previously_ran_at = @sync.last_ran_at
|
previously_ran_at = sync.last_ran_at
|
||||||
|
|
||||||
@sync.perform
|
sync.perform
|
||||||
|
|
||||||
assert @sync.last_ran_at > previously_ran_at
|
assert sync.last_ran_at > previously_ran_at
|
||||||
assert_equal "failed", @sync.status
|
assert_equal "failed", sync.status
|
||||||
assert_equal "test sync error", @sync.error
|
assert_equal "test sync error", sync.error
|
||||||
end
|
end
|
||||||
|
|
||||||
# Order is important here. Parent syncs must implement sync_data so that their own work
|
test "can run nested syncs that alert the parent when complete" do
|
||||||
# is 100% complete *prior* to queueing up child syncs.
|
# Clear out fixture syncs
|
||||||
test "runs sync with child syncs" do
|
Sync.destroy_all
|
||||||
|
|
||||||
|
# These fixtures represent a Parent -> Child -> Grandchild sync hierarchy
|
||||||
|
# Family -> PlaidItem -> Account
|
||||||
family = families(:dylan_family)
|
family = families(:dylan_family)
|
||||||
|
plaid_item = plaid_items(:one)
|
||||||
|
account = accounts(:connected)
|
||||||
|
|
||||||
parent = Sync.create!(syncable: family)
|
sync = Sync.create!(syncable: family)
|
||||||
child1 = Sync.create!(syncable: family.accounts.first, parent: parent)
|
|
||||||
child2 = Sync.create!(syncable: family.accounts.second, parent: parent)
|
|
||||||
grandchild = Sync.create!(syncable: family.accounts.last, parent: child2)
|
|
||||||
|
|
||||||
Family::Syncer.any_instance.expects(:perform_sync).with(parent, start_date: parent.start_date).once
|
Family::Syncer.any_instance.expects(:perform_sync).with(start_date: sync.start_date).once
|
||||||
Account::Syncer.any_instance.expects(:perform_sync).with(child1, start_date: parent.start_date).once
|
Family::Syncer.any_instance.expects(:perform_post_sync).once
|
||||||
Account::Syncer.any_instance.expects(:perform_sync).with(child2, start_date: parent.start_date).once
|
Family::Syncer.any_instance.expects(:child_syncables).returns([ plaid_item ])
|
||||||
Account::Syncer.any_instance.expects(:perform_sync).with(grandchild, start_date: parent.start_date).once
|
|
||||||
|
|
||||||
assert_equal "pending", parent.status
|
PlaidItem::Syncer.any_instance.expects(:perform_sync).with(start_date: sync.start_date).once
|
||||||
assert_equal "pending", child1.status
|
PlaidItem::Syncer.any_instance.expects(:perform_post_sync).once
|
||||||
assert_equal "pending", child2.status
|
PlaidItem::Syncer.any_instance.expects(:child_syncables).returns([ account ])
|
||||||
assert_equal "pending", grandchild.status
|
|
||||||
|
|
||||||
parent.perform
|
Account::Syncer.any_instance.expects(:perform_sync).with(start_date: sync.start_date).once
|
||||||
assert_equal "syncing", parent.reload.status
|
Account::Syncer.any_instance.expects(:perform_post_sync).once
|
||||||
|
Account::Syncer.any_instance.expects(:child_syncables).returns([])
|
||||||
|
|
||||||
child1.perform
|
sync.perform
|
||||||
assert_equal "completed", child1.reload.status
|
|
||||||
assert_equal "syncing", parent.reload.status
|
|
||||||
|
|
||||||
child2.perform
|
assert_equal 1, family.syncs.count
|
||||||
assert_equal "syncing", child2.reload.status
|
assert_equal "syncing", family.syncs.first.status
|
||||||
assert_equal "completed", child1.reload.status
|
assert_equal 1, plaid_item.syncs.count
|
||||||
assert_equal "syncing", parent.reload.status
|
assert_equal "pending", plaid_item.syncs.first.status
|
||||||
|
|
||||||
# Will complete the parent and grandparent syncs
|
# We have to perform jobs 2x because the child sync will schedule the grandchild sync,
|
||||||
grandchild.perform
|
# which then needs to be run.
|
||||||
assert_equal "completed", grandchild.reload.status
|
perform_enqueued_jobs
|
||||||
assert_equal "completed", child1.reload.status
|
|
||||||
assert_equal "completed", child2.reload.status
|
assert_equal 1, family.syncs.count
|
||||||
assert_equal "completed", parent.reload.status
|
assert_equal "syncing", family.syncs.first.status
|
||||||
|
assert_equal 1, plaid_item.syncs.count
|
||||||
|
assert_equal "syncing", plaid_item.syncs.first.status
|
||||||
|
assert_equal 1, account.syncs.count
|
||||||
|
assert_equal "pending", account.syncs.first.status
|
||||||
|
|
||||||
|
perform_enqueued_jobs
|
||||||
|
|
||||||
|
assert_equal 1, family.syncs.count
|
||||||
|
assert_equal "completed", family.syncs.first.status
|
||||||
|
assert_equal 1, plaid_item.syncs.count
|
||||||
|
assert_equal "completed", plaid_item.syncs.first.status
|
||||||
|
assert_equal 1, account.syncs.count
|
||||||
|
assert_equal "completed", account.syncs.first.status
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue