Files
sure/lib/tasks/dev_sync_stats.rake
Sophtron Rocky b32e9dbc45 Add Sophtron Provider (#596)
* Add Sophtron Provider

* fix syncer test issue

* fix schema  wrong merge

* sync #588

* sync code for #588

* fixed a view issue

* modified by comment

* modified

* modifed

* modified

* modified

* fixed a schema issue

* use global subtypes

* add some locales

* fix a safe_return_to_path

* fix exposing raw exception messages issue

* fix a merged issue

* update schema.rb

* fix a schema issue

* fix some issue

* Update bank sync controller to reflect beta status

Signed-off-by: Juan José Mata <jjmata@jjmata.com>

* Rename settings section title to 'Sophtron (alpha)'

Signed-off-by: Juan José Mata <jjmata@jjmata.com>

* Consistency in alpha/beta for Sophtron

* Good PR suggestions from CodeRabbit

---------

Signed-off-by: soky srm <sokysrm@gmail.com>
Signed-off-by: Sophtron Rocky <rocky@sophtron.com>
Signed-off-by: Juan José Mata <juanjo.mata@gmail.com>
Signed-off-by: Juan José Mata <jjmata@jjmata.com>
Co-authored-by: soky srm <sokysrm@gmail.com>
Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
Co-authored-by: Juan José Mata <jjmata@jjmata.com>
2026-04-19 11:16:04 +02:00

340 lines
12 KiB
Ruby

# frozen_string_literal: true
# Helper module for sync stats rake tasks
module DevSyncStatsHelpers
extend self
def generate_fake_stats_for_items(item_class, provider_name, include_issues: false)
items = item_class.all
if items.empty?
puts " No #{item_class.name} items found, skipping..."
return
end
items.each do |item|
# Create or find a sync record
sync = item.syncs.ordered.first
if sync.nil?
sync = item.syncs.create!(status: :completed, completed_at: Time.current)
end
stats = generate_fake_stats(provider_name, include_issues: include_issues)
sync.update!(sync_stats: stats, status: :completed, completed_at: Time.current)
item_name = item.respond_to?(:name) ? item.name : item.try(:institution_name) || item.id
puts " Generated stats for #{item_class.name} ##{item.id} (#{item_name})"
end
end
def generate_fake_stats(provider_name, include_issues: false)
# Base stats that all providers have
stats = {
"total_accounts" => rand(3..15),
"linked_accounts" => rand(2..10),
"unlinked_accounts" => rand(0..3),
"import_started" => true,
"window_start" => 1.hour.ago.iso8601,
"window_end" => Time.current.iso8601
}
# Ensure linked + unlinked <= total
stats["linked_accounts"] = [ stats["linked_accounts"], stats["total_accounts"] ].min
stats["unlinked_accounts"] = stats["total_accounts"] - stats["linked_accounts"]
# Add transaction stats for most providers
unless provider_name == "coinstats"
stats.merge!(
"tx_seen" => rand(50..500),
"tx_imported" => rand(10..100),
"tx_updated" => rand(0..50),
"tx_skipped" => rand(0..5)
)
# Ensure seen = imported + updated
stats["tx_seen"] = stats["tx_imported"] + stats["tx_updated"]
end
# Add holdings stats for investment-capable providers
if %w[simplefin plaid coinstats].include?(provider_name)
stats["holdings_found"] = rand(5..50)
end
# Add issues if requested
if include_issues
# Random chance of rate limiting
if rand < 0.3
stats["rate_limited"] = true
stats["rate_limited_at"] = rand(1..24).hours.ago.iso8601
end
# Random errors
if rand < 0.4
error_count = rand(1..3)
stats["errors"] = error_count.times.map do
{
"message" => [
"Connection timeout",
"Invalid credentials",
"Rate limit exceeded",
"Temporary API error"
].sample,
"category" => %w[api_error connection_error auth_error].sample
}
end
stats["total_errors"] = error_count
else
stats["total_errors"] = 0
end
# Data quality warnings
if rand < 0.5
stats["data_warnings"] = rand(1..8)
stats["notices"] = rand(0..3)
stats["data_quality_details"] = stats["data_warnings"].times.map do |i|
start_date = rand(30..180).days.ago.to_date
end_date = start_date + rand(14..60).days
gap_days = (end_date - start_date).to_i
{
"message" => "No transactions between #{start_date} and #{end_date} (#{gap_days} days)",
"severity" => gap_days > 30 ? "warning" : "info"
}
end
end
else
stats["total_errors"] = 0
end
stats
end
end
namespace :dev do
namespace :sync_stats do
desc "Generate fake sync stats for testing the sync summary UI"
task generate: :environment do
unless Rails.env.development?
puts "This task is only available in development mode"
exit 1
end
puts "Generating fake sync stats for testing..."
DevSyncStatsHelpers.generate_fake_stats_for_items(PlaidItem, "plaid")
DevSyncStatsHelpers.generate_fake_stats_for_items(SimplefinItem, "simplefin")
DevSyncStatsHelpers.generate_fake_stats_for_items(LunchflowItem, "lunchflow")
DevSyncStatsHelpers.generate_fake_stats_for_items(EnableBankingItem, "enable_banking")
DevSyncStatsHelpers.generate_fake_stats_for_items(CoinstatsItem, "coinstats")
DevSyncStatsHelpers.generate_fake_stats_for_items(SophtronItem, "sophtron")
puts "Done! Refresh your browser to see the sync summaries."
end
desc "Clear all sync stats from syncs"
task clear: :environment do
unless Rails.env.development?
puts "This task is only available in development mode"
exit 1
end
puts "Clearing all sync stats..."
Sync.where.not(sync_stats: nil).update_all(sync_stats: nil)
puts "Done!"
end
desc "Generate fake sync stats with errors and warnings for testing"
task generate_with_issues: :environment do
unless Rails.env.development?
puts "This task is only available in development mode"
exit 1
end
puts "Generating fake sync stats with errors and warnings..."
DevSyncStatsHelpers.generate_fake_stats_for_items(PlaidItem, "plaid", include_issues: true)
DevSyncStatsHelpers.generate_fake_stats_for_items(SimplefinItem, "simplefin", include_issues: true)
DevSyncStatsHelpers.generate_fake_stats_for_items(LunchflowItem, "lunchflow", include_issues: true)
DevSyncStatsHelpers.generate_fake_stats_for_items(EnableBankingItem, "enable_banking", include_issues: true)
DevSyncStatsHelpers.generate_fake_stats_for_items(CoinstatsItem, "coinstats", include_issues: true)
DevSyncStatsHelpers.generate_fake_stats_for_items(SophtronItem, "sophtron", include_issues: true)
puts "Done! Refresh your browser to see the sync summaries with issues."
end
desc "Create fake provider items with sync stats for testing (use when you have no provider connections)"
task create_test_providers: :environment do
unless Rails.env.development?
puts "This task is only available in development mode"
exit 1
end
family = Family.first
unless family
puts "No family found. Please create a user account first."
exit 1
end
puts "Creating fake provider items for family: #{family.name || family.id}..."
# Create a fake SimpleFIN item
simplefin_item = family.simplefin_items.create!(
name: "Test SimpleFIN Connection",
access_url: "https://test.simplefin.org/fake"
)
puts " Created SimplefinItem: #{simplefin_item.name}"
# Create fake SimpleFIN accounts
3.times do |i|
simplefin_item.simplefin_accounts.create!(
name: "Test Account #{i + 1}",
account_id: "test-account-#{SecureRandom.hex(8)}",
currency: "USD",
current_balance: rand(1000..50000),
account_type: %w[checking savings credit_card].sample
)
end
puts " Created 3 SimplefinAccounts"
# Create a fake Plaid item (requires access_token)
plaid_item = family.plaid_items.create!(
name: "Test Plaid Connection",
access_token: "test-access-token-#{SecureRandom.hex(16)}",
plaid_id: "test-plaid-id-#{SecureRandom.hex(8)}"
)
puts " Created PlaidItem: #{plaid_item.name}"
# Create fake Plaid accounts
2.times do |i|
plaid_item.plaid_accounts.create!(
name: "Test Plaid Account #{i + 1}",
plaid_id: "test-plaid-account-#{SecureRandom.hex(8)}",
currency: "USD",
current_balance: rand(1000..50000),
plaid_type: %w[depository credit investment].sample,
plaid_subtype: "checking"
)
end
puts " Created 2 PlaidAccounts"
# Create a fake Lunchflow item
lunchflow_item = family.lunchflow_items.create!(
name: "Test Lunchflow Connection",
api_key: "test-api-key-#{SecureRandom.hex(16)}"
)
puts " Created LunchflowItem: #{lunchflow_item.name}"
# Create fake Lunchflow accounts
2.times do |i|
lunchflow_item.lunchflow_accounts.create!(
name: "Test Lunchflow Account #{i + 1}",
account_id: "test-lunchflow-#{SecureRandom.hex(8)}",
currency: "USD",
current_balance: rand(1000..50000)
)
end
puts " Created 2 LunchflowAccounts"
# Create a fake Sophtron item
sophtron_item = family.sophtron_items.create!(
name: "Test Sophtron Connection",
user_id: "test-user-id-#{SecureRandom.hex(16)}",
access_key: "test-access-key-#{SecureRandom.hex(32)}"
)
puts " Created SophtronItem: #{sophtron_item.name}"
# Create fake Sophtron accounts
2.times do |i|
sophtron_item.sophtron_accounts.create!(
name: "Test Sophtron Account #{i + 1}",
account_id: "test-sophtron-#{SecureRandom.hex(8)}",
customer_id: "test-sophtron-#{SecureRandom.hex(8)}",
member_id: "test-sophtron-#{SecureRandom.hex(8)}",
currency: "USD",
current_balance: rand(1000..50000)
)
end
puts " Created 2 SophtronAccounts"
# Create a fake CoinStats item
coinstats_item = family.coinstats_items.create!(
name: "Test CoinStats Connection",
api_key: "test-coinstats-key-#{SecureRandom.hex(16)}",
institution_name: "CoinStats"
)
puts " Created CoinstatsItem: #{coinstats_item.name}"
# Create fake CoinStats accounts (wallets)
3.times do |i|
coinstats_item.coinstats_accounts.create!(
name: "Test Wallet #{i + 1}",
account_id: "test-wallet-#{SecureRandom.hex(8)}",
currency: "USD",
current_balance: rand(100..10000),
account_type: %w[wallet exchange defi].sample
)
end
puts " Created 3 CoinstatsAccounts"
# Create a fake EnableBanking item
begin
enable_banking_item = family.enable_banking_items.create!(
name: "Test EnableBanking Connection",
institution_name: "Test Bank EU",
institution_id: "test-bank-#{SecureRandom.hex(8)}",
country_code: "DE",
aspsp_name: "Test Bank",
aspsp_id: "test-aspsp-#{SecureRandom.hex(8)}",
application_id: "test-app-#{SecureRandom.hex(8)}",
client_certificate: "-----BEGIN CERTIFICATE-----\nTEST_CERTIFICATE\n-----END CERTIFICATE-----"
)
puts " Created EnableBankingItem: #{enable_banking_item.institution_name}"
# Create fake EnableBanking accounts
2.times do |i|
uid = "test-eb-uid-#{SecureRandom.hex(8)}"
enable_banking_item.enable_banking_accounts.create!(
name: "Test EU Account #{i + 1}",
uid: uid,
account_id: "test-eb-account-#{SecureRandom.hex(8)}",
currency: "EUR",
current_balance: rand(1000..50000),
iban: "DE#{rand(10..99)}#{SecureRandom.hex(10).upcase[0..17]}"
)
end
puts " Created 2 EnableBankingAccounts"
rescue => e
puts " Failed to create EnableBankingItem: #{e.message}"
end
puts "\nNow generating sync stats for the test providers..."
DevSyncStatsHelpers.generate_fake_stats_for_items(SimplefinItem, "simplefin", include_issues: true)
DevSyncStatsHelpers.generate_fake_stats_for_items(PlaidItem, "plaid", include_issues: false)
DevSyncStatsHelpers.generate_fake_stats_for_items(LunchflowItem, "lunchflow", include_issues: false)
DevSyncStatsHelpers.generate_fake_stats_for_items(CoinstatsItem, "coinstats", include_issues: true)
DevSyncStatsHelpers.generate_fake_stats_for_items(EnableBankingItem, "enable_banking", include_issues: false)
DevSyncStatsHelpers.generate_fake_stats_for_items(SophtronItem, "sophtron", include_issues: false)
puts "\nDone! Visit /accounts to see the sync summaries."
end
desc "Remove all test provider items created by create_test_providers"
task remove_test_providers: :environment do
unless Rails.env.development?
puts "This task is only available in development mode"
exit 1
end
puts "Removing test provider items..."
# Remove items that start with "Test "
count = 0
count += SimplefinItem.where("name LIKE ?", "Test %").destroy_all.count
count += PlaidItem.where("name LIKE ?", "Test %").destroy_all.count
count += LunchflowItem.where("name LIKE ?", "Test %").destroy_all.count
count += CoinstatsItem.where("name LIKE ?", "Test %").destroy_all.count
count += EnableBankingItem.where("name LIKE ? OR institution_name LIKE ?", "Test %", "Test %").destroy_all.count
count += SophtronItem.where("name LIKE ?", "Test %").destroy_all.count
puts "Removed #{count} test provider items. Done!"
end
end
end