mirror of
https://github.com/we-promise/sure.git
synced 2026-04-07 14:31:25 +00:00
* Implement providers factory * Multiple providers sync support - Proper Multi-Provider Syncing: When you click sync on an account with multiple providers (e.g., both Plaid and SimpleFin), all provider items are synced - Better API: The existing account.providers method already returns all providers, and account.provider returns the first one for backward compatibility - Correct Holdings Deletion Logic: Holdings can only be deleted if ALL providers allow it, preventing accidental deletions that would be recreated on next sync TODO: validate this is the way we want to go? We would need to check holdings belong to which account, and then check provider allows deletion. More complex - Database Constraints: The existing validations ensure an account can have at most one provider of each type (one PlaidAccount, one SimplefinAccount, etc.) * Add generic provider_import_adapter * Finish unified import strategy * Update app/models/plaid_account.rb Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Signed-off-by: soky srm <sokysrm@gmail.com> * Update app/models/provider/factory.rb Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Signed-off-by: soky srm <sokysrm@gmail.com> * Fix account linked by plaid_id instead of external_id * Parse numerics to BigDecimal Parse numerics to BigDecimal before computing amount; guard nils. Avoid String * String and float drift; also normalize date. * Fix incorrect usage of assert_raises. * Fix linter * Fix processor test. * Update current_balance_manager.rb * Test fixes * Fix plaid linked account test * Add support for holding per account_provider * Fix proper account access Also fix account deletion for simpefin too * FIX match tests for consistency * Some more factory updates * Fix account schema for multipe providers Can do: - Account #1 → PlaidAccount + SimplefinAccount (multiple different providers) - Account #2 → PlaidAccount only - Account #3 → SimplefinAccount only Cannot do: - Account #1 → PlaidAccount + PlaidAccount (duplicate provider type) - PlaidAccount #123 → Account #1 + Account #2 (provider linked to multiple accounts) * Fix account setup - An account CAN have multiple providers (the schema shows account_providers with unique index on [account_id, provider_type]) - Each provider should maintain its own separate entries - We should NOT update one provider's entry when another provider syncs * Fix linter and guard migration * FIX linter issues. * Fixes - Remove duplicated index - Pass account_provider_id - Guard holdings call to avoid NoMethodError * Update schema and provider import fix * Plaid doesn't allow holdings deletion * Use ClimateControl for proper env setup * No need for this in .git --------- Signed-off-by: soky srm <sokysrm@gmail.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
239 lines
8.0 KiB
Ruby
239 lines
8.0 KiB
Ruby
require "test_helper"
|
|
|
|
class SimplefinItemsControllerTest < ActionDispatch::IntegrationTest
|
|
setup do
|
|
sign_in users(:family_admin)
|
|
@family = families(:dylan_family)
|
|
@simplefin_item = SimplefinItem.create!(
|
|
family: @family,
|
|
name: "Test Connection",
|
|
access_url: "https://example.com/test_access"
|
|
)
|
|
end
|
|
|
|
test "should get index" do
|
|
get simplefin_items_url
|
|
assert_response :success
|
|
assert_includes response.body, @simplefin_item.name
|
|
end
|
|
|
|
test "should get new" do
|
|
get new_simplefin_item_url
|
|
assert_response :success
|
|
end
|
|
|
|
test "should show simplefin item" do
|
|
get simplefin_item_url(@simplefin_item)
|
|
assert_response :success
|
|
end
|
|
|
|
test "should destroy simplefin item" do
|
|
assert_difference("SimplefinItem.count", 0) do # doesn't actually delete immediately
|
|
delete simplefin_item_url(@simplefin_item)
|
|
end
|
|
|
|
assert_redirected_to accounts_path
|
|
@simplefin_item.reload
|
|
assert @simplefin_item.scheduled_for_deletion?
|
|
end
|
|
|
|
test "should sync simplefin item" do
|
|
post sync_simplefin_item_url(@simplefin_item)
|
|
assert_redirected_to accounts_path
|
|
end
|
|
|
|
test "should get edit" do
|
|
@simplefin_item.update!(status: :requires_update)
|
|
get edit_simplefin_item_url(@simplefin_item)
|
|
assert_response :success
|
|
end
|
|
|
|
test "should update simplefin item with valid token" do
|
|
@simplefin_item.update!(status: :requires_update)
|
|
|
|
# Mock the SimpleFin provider to prevent real API calls
|
|
mock_provider = mock()
|
|
mock_provider.expects(:claim_access_url).with("valid_token").returns("https://example.com/new_access")
|
|
mock_provider.expects(:get_accounts).returns({ accounts: [] }).at_least_once
|
|
Provider::Simplefin.expects(:new).returns(mock_provider).at_least_once
|
|
|
|
# Let the real create_simplefin_item! method run - don't mock it
|
|
|
|
patch simplefin_item_url(@simplefin_item), params: {
|
|
simplefin_item: { setup_token: "valid_token" }
|
|
}
|
|
|
|
assert_redirected_to accounts_path
|
|
assert_match(/updated successfully/, flash[:notice])
|
|
@simplefin_item.reload
|
|
assert @simplefin_item.scheduled_for_deletion?
|
|
end
|
|
|
|
test "should handle update with invalid token" do
|
|
@simplefin_item.update!(status: :requires_update)
|
|
|
|
patch simplefin_item_url(@simplefin_item), params: {
|
|
simplefin_item: { setup_token: "" }
|
|
}
|
|
|
|
assert_response :unprocessable_entity
|
|
assert_includes response.body, "Please enter a SimpleFin setup token"
|
|
end
|
|
|
|
test "should transfer accounts when updating simplefin item token" do
|
|
@simplefin_item.update!(status: :requires_update)
|
|
|
|
# Create old SimpleFin accounts linked to Maybe accounts
|
|
old_simplefin_account1 = @simplefin_item.simplefin_accounts.create!(
|
|
name: "Test Checking",
|
|
account_id: "sf_account_123",
|
|
currency: "USD",
|
|
current_balance: 1000,
|
|
account_type: "depository"
|
|
)
|
|
old_simplefin_account2 = @simplefin_item.simplefin_accounts.create!(
|
|
name: "Test Savings",
|
|
account_id: "sf_account_456",
|
|
currency: "USD",
|
|
current_balance: 5000,
|
|
account_type: "depository"
|
|
)
|
|
|
|
# Create Maybe accounts linked to the SimpleFin accounts
|
|
maybe_account1 = Account.create!(
|
|
family: @family,
|
|
name: "Checking Account",
|
|
balance: 1000,
|
|
currency: "USD",
|
|
accountable_type: "Depository",
|
|
accountable: Depository.create!(subtype: "checking"),
|
|
simplefin_account_id: old_simplefin_account1.id
|
|
)
|
|
maybe_account2 = Account.create!(
|
|
family: @family,
|
|
name: "Savings Account",
|
|
balance: 5000,
|
|
currency: "USD",
|
|
accountable_type: "Depository",
|
|
accountable: Depository.create!(subtype: "savings"),
|
|
simplefin_account_id: old_simplefin_account2.id
|
|
)
|
|
|
|
# Update old SimpleFin accounts to reference the Maybe accounts
|
|
old_simplefin_account1.update!(account: maybe_account1)
|
|
old_simplefin_account2.update!(account: maybe_account2)
|
|
|
|
# Mock only the external API calls, let business logic run
|
|
mock_provider = mock()
|
|
mock_provider.expects(:claim_access_url).with("valid_token").returns("https://example.com/new_access")
|
|
mock_provider.expects(:get_accounts).returns({
|
|
accounts: [
|
|
{
|
|
id: "sf_account_123",
|
|
name: "Test Checking",
|
|
type: "depository",
|
|
currency: "USD",
|
|
balance: 1000,
|
|
transactions: []
|
|
},
|
|
{
|
|
id: "sf_account_456",
|
|
name: "Test Savings",
|
|
type: "depository",
|
|
currency: "USD",
|
|
balance: 5000,
|
|
transactions: []
|
|
}
|
|
]
|
|
}).at_least_once
|
|
Provider::Simplefin.expects(:new).returns(mock_provider).at_least_once
|
|
|
|
# Perform the update
|
|
patch simplefin_item_url(@simplefin_item), params: {
|
|
simplefin_item: { setup_token: "valid_token" }
|
|
}
|
|
|
|
assert_redirected_to accounts_path
|
|
assert_match(/updated successfully/, flash[:notice])
|
|
|
|
# Verify accounts were transferred to new SimpleFin accounts
|
|
assert Account.exists?(maybe_account1.id), "maybe_account1 should still exist"
|
|
assert Account.exists?(maybe_account2.id), "maybe_account2 should still exist"
|
|
|
|
maybe_account1.reload
|
|
maybe_account2.reload
|
|
|
|
# Find the new SimpleFin item that was created
|
|
new_simplefin_item = @family.simplefin_items.where.not(id: @simplefin_item.id).first
|
|
assert_not_nil new_simplefin_item, "New SimpleFin item should have been created"
|
|
|
|
new_sf_account1 = new_simplefin_item.simplefin_accounts.find_by(account_id: "sf_account_123")
|
|
new_sf_account2 = new_simplefin_item.simplefin_accounts.find_by(account_id: "sf_account_456")
|
|
|
|
assert_not_nil new_sf_account1, "New SimpleFin account with ID sf_account_123 should exist"
|
|
assert_not_nil new_sf_account2, "New SimpleFin account with ID sf_account_456 should exist"
|
|
|
|
assert_equal new_sf_account1.id, maybe_account1.simplefin_account_id
|
|
assert_equal new_sf_account2.id, maybe_account2.simplefin_account_id
|
|
|
|
# Verify old SimpleFin accounts no longer reference Maybe accounts
|
|
old_simplefin_account1.reload
|
|
old_simplefin_account2.reload
|
|
assert_nil old_simplefin_account1.current_account
|
|
assert_nil old_simplefin_account2.current_account
|
|
|
|
# Verify old SimpleFin item is scheduled for deletion
|
|
@simplefin_item.reload
|
|
assert @simplefin_item.scheduled_for_deletion?
|
|
end
|
|
|
|
test "should handle partial account matching during token update" do
|
|
@simplefin_item.update!(status: :requires_update)
|
|
|
|
# Create old SimpleFin account
|
|
old_simplefin_account = @simplefin_item.simplefin_accounts.create!(
|
|
name: "Test Checking",
|
|
account_id: "sf_account_123",
|
|
currency: "USD",
|
|
current_balance: 1000,
|
|
account_type: "depository"
|
|
)
|
|
|
|
# Create Maybe account linked to the SimpleFin account
|
|
maybe_account = Account.create!(
|
|
family: @family,
|
|
name: "Checking Account",
|
|
balance: 1000,
|
|
currency: "USD",
|
|
accountable_type: "Depository",
|
|
accountable: Depository.create!(subtype: "checking"),
|
|
simplefin_account_id: old_simplefin_account.id
|
|
)
|
|
old_simplefin_account.update!(account: maybe_account)
|
|
|
|
# Mock only the external API calls, let business logic run
|
|
mock_provider = mock()
|
|
mock_provider.expects(:claim_access_url).with("valid_token").returns("https://example.com/new_access")
|
|
# Return empty accounts list to simulate account was removed from bank
|
|
mock_provider.expects(:get_accounts).returns({ accounts: [] }).at_least_once
|
|
Provider::Simplefin.expects(:new).returns(mock_provider).at_least_once
|
|
|
|
# Perform update
|
|
patch simplefin_item_url(@simplefin_item), params: {
|
|
simplefin_item: { setup_token: "valid_token" }
|
|
}
|
|
|
|
assert_redirected_to accounts_path
|
|
|
|
# Verify Maybe account still linked to old SimpleFin account (no transfer occurred)
|
|
maybe_account.reload
|
|
old_simplefin_account.reload
|
|
assert_equal old_simplefin_account.id, maybe_account.simplefin_account_id
|
|
assert_equal maybe_account, old_simplefin_account.current_account
|
|
|
|
# Old item still scheduled for deletion
|
|
@simplefin_item.reload
|
|
assert @simplefin_item.scheduled_for_deletion?
|
|
end
|
|
end
|