Files
sure/test/models/trading212_account_test.rb
jdcdp 849578a84a Add support for trading212 integration in investments sync (#2513)
* Add support for trading212 integration in investments sync

* Respect Trading 212 history rate limits

* Replace hex colors with design tokens

* Changed withdrawal to withdraw

* Address PR review feedback for Trading 212 integration

- Replace all Rails.logger calls with DebugLogEntry.capture across
  syncer, importer, processor, provider, controller, and unlinking
- Fix destroy action to surface unlink errors via DebugLogEntry
- Replace hardcoded 'GBP' currency fallback with Current.family.currency
- Replace hardcoded English status strings in Syncer with i18n
- Add config/initializers/trading212.rb with DEBUG_RAW ENV toggle
- Add comprehensive test coverage: item, account, data_helpers,
  holdings_processor, activities_processor, importer, syncer,
  and controller tests
- Rewrite syncer and controller tests to match actual interfaces

* Fix tests and bugs found during local test execution

- Fix standard_ticker: empty string caused nil.upcase via [].first
- Fix parse_date: DateTime < Date, so when Date caught DateTime first
- Fix ActivitiesProcessor/HoldingsProcessor: missing instruments_map
  in DataHelpers caused NameError when processing dividends
- Fix test: security fixture ticker collision with test data
- Fix test: sync_status_summary i18n matching in assertions
- Fix test: trading212_provider ConfigurationError test path
- Fix test: controller invalid params needs Turbo-Frame header
- Fix test: syncer test uses stubs instead of strict expects

116 tests, 257 assertions, 0 failures, 0 errors

* Keep raw provider response bodies out of exception messages.

* Fix Secrets leak into page HTML, prevents the API key/secret from appearing in the HTML source while keeping the "leave blank to keep existing" UX.

* Address review findings: env gate, sync test, uniqueness test, destroy flow

- Gate TRADING212_DEBUG_RAW behind Rails.env.local? so staging/production
  cannot accidentally enable raw payload logging
- Assert SyncJob enqueue in sync controller test, not just redirect
- Fix cross-item uniqueness test to actually use two different items
- Remove rescue in destroy so unlink failures stop the flow (matching
  Brex/Akahu pattern) instead of proceeding to destroy_later silently

* Add Trading212 tables to db/schema.rb for CI test database

CI runs db:test:prepare which loads db/schema.rb, not migrations.
Without these table definitions, fixture loading fails with
PG::UndefinedTable: relation "trading212_accounts" does not exist.

* Removed lint complaint

* Register Trading212 in ProviderConnectionStatus::PROVIDERS

Fixes CI failure: test_provider_registry_covers_syncable_family_provider_item_associations

* Fixed bad merge

---------

Signed-off-by: jdcdp <47483528+jdcdp@users.noreply.github.com>
Co-authored-by: jdcdp <jdcdp@cdm4.net>
2026-07-25 04:54:30 +02:00

133 lines
3.8 KiB
Ruby

require "test_helper"
class Trading212AccountTest < ActiveSupport::TestCase
setup do
@family = families(:dylan_family)
@trading212_item = trading212_items(:configured_item)
@trading212_account = trading212_accounts(:main_account)
end
# === Validations ===
test "validates presence of currency" do
account = Trading212Account.new(
trading212_item: @trading212_item,
name: "Test Account",
trading212_account_id: "t212_test_1"
# currency intentionally blank
)
assert_not account.valid?
assert_includes account.errors[:currency], "can't be blank"
end
test "validates uniqueness of trading212_account_id within item scope" do
existing = @trading212_item.trading212_accounts.create!(
name: "Duplicate",
trading212_account_id: "t212_duplicate",
currency: "USD"
)
duplicate = @trading212_item.trading212_accounts.build(
name: "Another Duplicate",
trading212_account_id: "t212_duplicate",
currency: "USD"
)
assert_not duplicate.valid?
assert_includes duplicate.errors[:trading212_account_id], "has already been taken"
end
test "allows nil trading212_account_id across items" do
second_item = trading212_items(:pending_setup_item)
account1 = @trading212_item.trading212_accounts.create!(
name: "Account 1",
currency: "USD"
)
account2 = second_item.trading212_accounts.create!(
name: "Account 2",
currency: "USD"
)
# Both nil IDs are allowed across different items (unique index has WHERE clause)
assert account1.valid?
assert account2.valid?
end
# === current_account ===
test "current_account returns account through account_provider" do
investment = accounts(:investment)
@trading212_account.ensure_account_provider!(investment)
assert_equal investment, @trading212_account.current_account
end
test "current_account returns nil when no provider link" do
assert_nil @trading212_account.current_account
end
# === ensure_account_provider! ===
test "ensure_account_provider! creates AccountProvider for the given account" do
investment = accounts(:investment)
assert_difference "AccountProvider.count", 1 do
@trading212_account.ensure_account_provider!(investment)
end
assert_equal investment, @trading212_account.reload.current_account
end
test "ensure_account_provider! is idempotent" do
investment = accounts(:investment)
assert_difference "AccountProvider.count", 1 do
@trading212_account.ensure_account_provider!(investment)
end
assert_no_difference "AccountProvider.count" do
@trading212_account.ensure_account_provider!(investment)
end
assert_equal investment, @trading212_account.reload.current_account
end
test "ensure_account_provider! updates account if different" do
investment = accounts(:investment)
@trading212_account.ensure_account_provider!(investment)
crypto = accounts(:crypto)
@trading212_account.ensure_account_provider!(crypto)
assert_equal crypto, @trading212_account.reload.current_account
end
# === instruments_map ===
test "instruments_map delegates to trading212_item" do
instruments = [
{ "ticker" => "AAPL_US_EQ", "shortName" => "Apple Inc.", "currencyCode" => "USD" }
]
@trading212_item.update!(raw_instruments_payload: instruments)
result = @trading212_account.instruments_map
assert_equal "Apple Inc.", result["AAPL_US_EQ"]["shortName"]
end
# === belongs_to relationships ===
test "belongs to trading212_item" do
assert_equal @trading212_item, @trading212_account.trading212_item
end
test "destroyed when trading212_item is destroyed" do
t212_account_id = @trading212_account.id
@trading212_item.destroy
assert_nil Trading212Account.find_by(id: t212_account_id)
end
end