Back to Repositories

Testing Account Trade Operations in maybe-finance

This test suite validates the functionality of trade operations in a financial application, covering various transaction types including trades, deposits, withdrawals, and interest calculations. The tests ensure proper handling of account entries, transactions, and trade operations.

Test Coverage Overview

The test suite provides comprehensive coverage of account trading operations:

  • Trade entry updates and modifications
  • Deposit and withdrawal transactions with transfer handling
  • Interest entry creation and validation
  • Buy and sell trade operations with security tracking
  • Currency handling and amount validations

Implementation Analysis

The testing approach utilizes Rails’ ActionDispatch::IntegrationTest framework with Minitest assertions. It implements EntryableResourceInterfaceTest for shared behavior and employs setup fixtures for user authentication and initial trade entries.

The tests verify database changes using assert_difference blocks and validate transaction integrity through multiple model updates.

Technical Details

  • Testing Framework: Minitest with Rails integration testing
  • Fixtures: User and account data setup
  • Authentication: Sign-in helper methods
  • Job Queue: SyncJob verification
  • Models: Account::Entry, Account::Trade, Account::Transaction, Account::Transfer

Best Practices Demonstrated

The test suite exemplifies robust testing practices with comprehensive setup and teardown management. It includes thorough assertion chains, proper isolation of test cases, and validation of both positive and negative scenarios.

Notable practices include transaction counting, proper model relationship testing, and verification of redirects and flash messages.

maybe-finance/maybe

test/controllers/account/trades_controller_test.rb

            
require "test_helper"

class Account::TradesControllerTest < ActionDispatch::IntegrationTest
  include EntryableResourceInterfaceTest

  setup do
    sign_in @user = users(:family_admin)
    @entry = account_entries(:trade)
  end

  test "updates trade entry" do
    assert_no_difference [ "Account::Entry.count", "Account::Trade.count" ] do
      patch account_trade_url(@entry), params: {
        account_entry: {
          currency: "USD",
          entryable_attributes: {
            id: @entry.entryable_id,
            qty: 20,
            price: 20
          }
        }
      }
    end

    @entry.reload

    assert_enqueued_with job: SyncJob

    assert_equal 20, @entry.account_trade.qty
    assert_equal 20, @entry.account_trade.price
    assert_equal "USD", @entry.currency

    assert_redirected_to account_url(@entry.account)
  end

  test "creates deposit entry" do
    from_account = accounts(:depository) # Account the deposit is coming from

    assert_difference -> { Account::Entry.count } => 2,
                      -> { Account::Transaction.count } => 2,
                      -> { Account::Transfer.count } => 1 do
      post account_trades_url, params: {
        account_entry: {
          account_id: @entry.account_id,
          type: "deposit",
          date: Date.current,
          amount: 10,
          currency: "USD",
          transfer_account_id: from_account.id
        }
      }
    end

    assert_redirected_to @entry.account
  end

  test "creates withdrawal entry" do
    to_account = accounts(:depository) # Account the withdrawal is going to

    assert_difference -> { Account::Entry.count } => 2,
                      -> { Account::Transaction.count } => 2,
                      -> { Account::Transfer.count } => 1 do
      post account_trades_url, params: {
        account_entry: {
          account_id: @entry.account_id,
          type: "withdrawal",
          date: Date.current,
          amount: 10,
          currency: "USD",
          transfer_account_id: to_account.id
        }
      }
    end

    assert_redirected_to @entry.account
  end

  test "deposit and withdrawal has optional transfer account" do
    assert_difference -> { Account::Entry.count } => 1,
                      -> { Account::Transaction.count } => 1,
                      -> { Account::Transfer.count } => 0 do
      post account_trades_url, params: {
        account_entry: {
          account_id: @entry.account_id,
          type: "withdrawal",
          date: Date.current,
          amount: 10,
          currency: "USD"
        }
      }
    end

    created_entry = Account::Entry.order(created_at: :desc).first

    assert created_entry.amount.positive?
    assert created_entry.marked_as_transfer
    assert_redirected_to @entry.account
  end

  test "creates interest entry" do
    assert_difference [ "Account::Entry.count", "Account::Transaction.count" ], 1 do
      post account_trades_url, params: {
        account_entry: {
          account_id: @entry.account_id,
          type: "interest",
          date: Date.current,
          amount: 10,
          currency: "USD"
        }
      }
    end

    created_entry = Account::Entry.order(created_at: :desc).first

    assert created_entry.amount.negative?
    assert_redirected_to @entry.account
  end

  test "creates trade buy entry" do
    assert_difference [ "Account::Entry.count", "Account::Trade.count", "Security.count" ], 1 do
      post account_trades_url, params: {
        account_entry: {
          account_id: @entry.account_id,
          type: "buy",
          date: Date.current,
          ticker: "NVDA (NASDAQ)",
          qty: 10,
          price: 10,
          currency: "USD"
        }
      }
    end

    created_entry = Account::Entry.order(created_at: :desc).first

    assert created_entry.amount.positive?
    assert created_entry.account_trade.qty.positive?
    assert_equal "Entry created", flash[:notice]
    assert_enqueued_with job: SyncJob
    assert_redirected_to account_url(created_entry.account)
  end

  test "creates trade sell entry" do
    assert_difference [ "Account::Entry.count", "Account::Trade.count" ], 1 do
      post account_trades_url, params: {
        account_entry: {
          account_id: @entry.account_id,
          type: "sell",
          ticker: "AAPL (NYSE)",
          date: Date.current,
          currency: "USD",
          qty: 10,
          price: 10
        }
      }
    end

    created_entry = Account::Entry.order(created_at: :desc).first

    assert created_entry.amount.negative?
    assert created_entry.account_trade.qty.negative?
    assert_equal "Entry created", flash[:notice]
    assert_enqueued_with job: SyncJob
    assert_redirected_to account_url(created_entry.account)
  end
end