chore: init commit
in worker.../repo/GITFOLDER.zip is the .git folder.
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "test_helper"
|
||||
|
||||
class BulkStudentImportServiceTest < ActiveSupport::TestCase
|
||||
include CsvTestHelper
|
||||
|
||||
def setup
|
||||
@classroom = create(:classroom)
|
||||
@csv_header = "classroom_id,username"
|
||||
end
|
||||
|
||||
test "parses CSV and calls ImportStudentService for each valid row" do
|
||||
csv_content = [
|
||||
@csv_header,
|
||||
"#{@classroom.id},student001",
|
||||
"#{@classroom.id},student002"
|
||||
].join("\n")
|
||||
|
||||
with_temp_csv_file(csv_content) do |csv_file|
|
||||
results = BulkStudentImportService.import_from_csv(csv_file.path)
|
||||
|
||||
assert_equal 2, results.length
|
||||
assert_equal [2, 3], results.map(&:line_number)
|
||||
assert(results.all? { |item| item.respond_to?(:created?) })
|
||||
end
|
||||
end
|
||||
|
||||
test "skips empty rows and processes only valid rows" do
|
||||
csv_content = [
|
||||
@csv_header,
|
||||
"#{@classroom.id},student001",
|
||||
",",
|
||||
" , ",
|
||||
"#{@classroom.id},student002"
|
||||
].join("\n")
|
||||
|
||||
with_temp_csv_file(csv_content) do |csv_file|
|
||||
results = BulkStudentImportService.import_from_csv(csv_file.path)
|
||||
|
||||
assert_equal 2, results.length
|
||||
assert_equal [2, 5], results.map(&:line_number)
|
||||
end
|
||||
end
|
||||
|
||||
test "handles malformed CSV files" do
|
||||
csv_content = "classroom_id,username\n1,\"unclosed quote\n2,another_row"
|
||||
|
||||
with_temp_csv_file(csv_content) do |csv_file|
|
||||
assert_raises(CSV::MalformedCSVError) do
|
||||
BulkStudentImportService.import_from_csv(csv_file.path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "returns empty array for CSV with no data rows" do
|
||||
csv_content = @csv_header
|
||||
|
||||
with_temp_csv_file(csv_content) do |csv_file|
|
||||
results = BulkStudentImportService.import_from_csv(csv_file.path)
|
||||
assert_equal [], results
|
||||
end
|
||||
end
|
||||
|
||||
test "returns empty array for CSV with only empty rows" do
|
||||
csv_content = [
|
||||
@csv_header,
|
||||
",",
|
||||
" , "
|
||||
].join("\n")
|
||||
|
||||
with_temp_csv_file(csv_content) do |csv_file|
|
||||
results = BulkStudentImportService.import_from_csv(csv_file.path)
|
||||
assert_equal [], results
|
||||
end
|
||||
end
|
||||
|
||||
test "generate_csv_template returns correct format" do
|
||||
template = BulkStudentImportService.generate_csv_template
|
||||
lines = template.split("\n")
|
||||
|
||||
assert_equal "classroom_id,username", lines.first
|
||||
assert_equal 4, lines.length
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,86 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "test_helper"
|
||||
|
||||
class DistributeEarningsTest < ActiveSupport::TestCase
|
||||
test "only processes verified grade books" do
|
||||
draft_grade_book = create(:grade_book, status: :draft)
|
||||
completed_grade_book = create(:grade_book, status: :completed)
|
||||
|
||||
assert_no_changes -> { PortfolioTransaction.count } do
|
||||
[draft_grade_book, completed_grade_book].each do |grade_book|
|
||||
DistributeEarnings.execute(grade_book)
|
||||
end
|
||||
|
||||
assert draft_grade_book.draft?
|
||||
assert completed_grade_book.completed?
|
||||
end
|
||||
end
|
||||
|
||||
test "skips transactions with zero earnings" do
|
||||
student = create(:student)
|
||||
grade_book = create(:grade_book, status: :verified)
|
||||
create(:grade_entry, grade_book: grade_book, user: student, attendance_days: 0, math_grade: "F", reading_grade: "F")
|
||||
|
||||
assert_no_difference -> { student.portfolio.portfolio_transactions.count } do
|
||||
DistributeEarnings.execute(grade_book)
|
||||
end
|
||||
end
|
||||
|
||||
test "distributes earnings as separate transactions by category" do
|
||||
school_year = create(:school_year)
|
||||
previous_quarter = school_year.quarters.find_by!(number: 1)
|
||||
current_quarter = school_year.quarters.find_by!(number: 2)
|
||||
|
||||
student = create(:student)
|
||||
classroom = create(:classroom)
|
||||
|
||||
previous_grade_book = create(:grade_book, quarter: previous_quarter, classroom: classroom)
|
||||
create(
|
||||
:grade_entry,
|
||||
grade_book: previous_grade_book,
|
||||
user: student,
|
||||
attendance_days: 6,
|
||||
math_grade: "C",
|
||||
reading_grade: "C"
|
||||
)
|
||||
previous_grade_book.completed!
|
||||
|
||||
current_grade_book = create(:grade_book, quarter: current_quarter, classroom: classroom, status: :verified)
|
||||
current_entry = create(
|
||||
:grade_entry,
|
||||
grade_book: current_grade_book,
|
||||
user: student,
|
||||
attendance_days: 12,
|
||||
math_grade: "B",
|
||||
reading_grade: "A"
|
||||
)
|
||||
|
||||
DistributeEarnings.execute(current_grade_book)
|
||||
|
||||
current_grade_book.reload
|
||||
assert current_grade_book.completed?
|
||||
|
||||
transactions = student.portfolio.portfolio_transactions
|
||||
assert_equal 3, transactions.count
|
||||
|
||||
expected_attendance = current_entry.earnings_for_attendance
|
||||
expected_math = GradeEntry::EARNINGS_FOR_B_GRADE + GradeEntry::EARNINGS_FOR_IMPROVED_GRADE
|
||||
expected_reading = GradeEntry::EARNINGS_FOR_A_GRADE + GradeEntry::EARNINGS_FOR_IMPROVED_GRADE
|
||||
|
||||
attendance_transaction = transactions.find(&:attendance_earnings?)
|
||||
assert_not_nil attendance_transaction
|
||||
assert_equal expected_attendance, attendance_transaction.amount_cents
|
||||
assert attendance_transaction.deposit?
|
||||
|
||||
math_transaction = transactions.find(&:math_earnings?)
|
||||
assert_not_nil math_transaction
|
||||
assert_equal expected_math, math_transaction.amount_cents
|
||||
assert math_transaction.deposit?
|
||||
|
||||
reading_transaction = transactions.find(&:reading_earnings?)
|
||||
assert_not_nil reading_transaction
|
||||
assert_equal expected_reading, reading_transaction.amount_cents
|
||||
assert reading_transaction.deposit?
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,180 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "test_helper"
|
||||
|
||||
class ExecuteOrderTest < ActiveSupport::TestCase
|
||||
test "it creates a debit transaction in portfolio_transactions" do
|
||||
student = create(:student)
|
||||
create(:portfolio, user: student)
|
||||
student.portfolio.portfolio_transactions.create!(amount_cents: 1000, transaction_type: :deposit)
|
||||
stock = create(:stock, price_cents: 100)
|
||||
order = create(:order, :pending, action: :buy, shares: 5, stock: stock, user: student)
|
||||
|
||||
assert_difference("PortfolioTransaction.count") do
|
||||
ExecuteOrder.execute(order)
|
||||
end
|
||||
|
||||
portfolio_transaction = PortfolioTransaction.last
|
||||
assert portfolio_transaction.debit?
|
||||
assert_equal portfolio_transaction, order.portfolio_transaction
|
||||
assert_operator portfolio_transaction.amount_cents, :>, 0
|
||||
end
|
||||
|
||||
test "it creates an linked entry in portfolio_stocks" do
|
||||
student = create(:student)
|
||||
create(:portfolio, user: student)
|
||||
student.portfolio.portfolio_transactions.create!(amount_cents: 1000, transaction_type: :deposit)
|
||||
stock = create(:stock, price_cents: 100)
|
||||
order = create(:order, :pending, action: :buy, shares: 5, stock: stock, user: student)
|
||||
|
||||
assert_difference("PortfolioStock.count") do
|
||||
ExecuteOrder.execute(order)
|
||||
end
|
||||
|
||||
portfolio_stock = PortfolioStock.last
|
||||
assert_equal portfolio_stock, order.portfolio_stock
|
||||
end
|
||||
|
||||
test "it updates the order status to completed" do
|
||||
student = create(:student)
|
||||
create(:portfolio, user: student)
|
||||
student.portfolio.portfolio_transactions.create!(amount_cents: 1000, transaction_type: :deposit)
|
||||
stock = create(:stock, price_cents: 100)
|
||||
order = create(:order, :pending, action: :buy, shares: 5, stock: stock, user: student)
|
||||
|
||||
ExecuteOrder.execute(order)
|
||||
order.reload
|
||||
|
||||
assert order.completed?
|
||||
end
|
||||
|
||||
test "it does not update order when status is not pending" do
|
||||
student = create(:student)
|
||||
student.portfolio.portfolio_transactions.create!(amount_cents: 1000, transaction_type: :deposit)
|
||||
stock = create(:stock, price_cents: 100)
|
||||
order = create(:order, :completed, action: :buy, shares: 5, stock: stock, user: student)
|
||||
|
||||
ExecuteOrder.execute(order)
|
||||
order.reload
|
||||
|
||||
assert order.completed?
|
||||
end
|
||||
|
||||
test "it handles transaction rollback when portfolio stock creation fails" do
|
||||
portfolio = create(:portfolio)
|
||||
portfolio.portfolio_transactions.create!(amount_cents: 1000, transaction_type: :deposit)
|
||||
|
||||
stock = create(:stock, price_cents: 100)
|
||||
order = create(:order, :pending, :buy, shares: 5, stock: stock, user: portfolio.user)
|
||||
|
||||
PortfolioStock.any_instance.stubs(:save!).raises(ActiveRecord::RecordInvalid.new(PortfolioStock.new))
|
||||
|
||||
assert_raises(ActiveRecord::RecordInvalid) do
|
||||
ExecuteOrder.execute(order)
|
||||
end
|
||||
|
||||
order.reload
|
||||
|
||||
assert_equal "pending", order.status
|
||||
assert_nil order.portfolio_transaction
|
||||
assert_nil order.portfolio_stock
|
||||
end
|
||||
|
||||
test "buy order creates positive shares in portfolio_stock" do
|
||||
portfolio = create(:portfolio)
|
||||
portfolio.portfolio_transactions.create!(amount_cents: 1000, transaction_type: :deposit)
|
||||
stock = create(:stock, price_cents: 100)
|
||||
order = create(:order, :pending, :buy, shares: 5, stock: stock, user: portfolio.user)
|
||||
|
||||
ExecuteOrder.execute(order)
|
||||
|
||||
portfolio_stock = PortfolioStock.last
|
||||
assert_equal 5, portfolio_stock.shares
|
||||
assert_equal stock, portfolio_stock.stock
|
||||
end
|
||||
|
||||
test "sell order creates negative shares in portfolio_stock" do
|
||||
portfolio = create(:portfolio)
|
||||
stock = create(:stock, price_cents: 100)
|
||||
create(:portfolio_stock, portfolio: portfolio, stock: stock, shares: 10, purchase_price: 100)
|
||||
|
||||
order = create(:order, :pending, :sell, shares: 3, stock: stock, user: portfolio.user)
|
||||
|
||||
ExecuteOrder.execute(order)
|
||||
|
||||
portfolio_stocks = PortfolioStock.where(portfolio: portfolio, stock: stock)
|
||||
assert_equal 2, portfolio_stocks.count
|
||||
|
||||
sell_record = portfolio_stocks.order(:created_at).last
|
||||
assert_equal(-3, sell_record.shares)
|
||||
assert_equal stock, sell_record.stock
|
||||
end
|
||||
|
||||
test "multiple buy orders create separate portfolio_stock records" do
|
||||
portfolio = create(:portfolio)
|
||||
portfolio.portfolio_transactions.create!(amount_cents: 1000, transaction_type: :deposit)
|
||||
stock = create(:stock, price_cents: 100)
|
||||
|
||||
order1 = create(:order, :pending, :buy, shares: 5, stock: stock, user: portfolio.user)
|
||||
ExecuteOrder.execute(order1)
|
||||
|
||||
order2 = create(:order, :pending, :buy, shares: 3, stock: stock, user: portfolio.user)
|
||||
ExecuteOrder.execute(order2)
|
||||
|
||||
portfolio_stocks = PortfolioStock.where(stock: stock, portfolio: portfolio)
|
||||
assert_equal 2, portfolio_stocks.count
|
||||
assert_equal [3, 5], portfolio_stocks.pluck(:shares).sort
|
||||
end
|
||||
|
||||
test "buy order with insufficient funds is canceled" do
|
||||
portfolio = create(:portfolio)
|
||||
portfolio.portfolio_transactions.create!(amount_cents: 10_000, transaction_type: :deposit)
|
||||
stock = create(:stock, price_cents: 5000)
|
||||
order = create(:order, :pending, :buy, shares: 1, stock: stock, user: portfolio.user)
|
||||
|
||||
# Simulate funds disappearing after order creation
|
||||
portfolio.portfolio_transactions.create!(amount_cents: 9000, transaction_type: :withdrawal)
|
||||
|
||||
ExecuteOrder.execute(order)
|
||||
order.reload
|
||||
|
||||
assert order.canceled?
|
||||
assert_equal 0, PortfolioStock.where(portfolio: portfolio, stock: stock).count
|
||||
end
|
||||
|
||||
test "sell order with insufficient shares is canceled" do
|
||||
portfolio = create(:portfolio)
|
||||
stock = create(:stock, price_cents: 100)
|
||||
create(:portfolio_stock, portfolio: portfolio, stock: stock, shares: 10, purchase_price: 100)
|
||||
order = create(:order, :pending, :sell, shares: 5, stock: stock, user: portfolio.user)
|
||||
|
||||
# Simulate shares disappearing after order creation (e.g., another sell executed first)
|
||||
create(:portfolio_stock, portfolio: portfolio, stock: stock, shares: -8, purchase_price: 100)
|
||||
|
||||
ExecuteOrder.execute(order)
|
||||
order.reload
|
||||
|
||||
assert order.canceled?
|
||||
end
|
||||
|
||||
test "buy then sell orders work together correctly" do
|
||||
portfolio = create(:portfolio)
|
||||
portfolio.portfolio_transactions.create!(amount_cents: 2000, transaction_type: :deposit)
|
||||
stock = create(:stock, price_cents: 100)
|
||||
|
||||
buy_order = create(:order, :pending, :buy, shares: 10, stock: stock, user: portfolio.user)
|
||||
ExecuteOrder.execute(buy_order)
|
||||
|
||||
sell_order = create(:order, :pending, :sell, shares: 4, stock: stock, user: portfolio.user)
|
||||
ExecuteOrder.execute(sell_order)
|
||||
|
||||
portfolio_stocks = PortfolioStock.where(stock: stock, portfolio: portfolio)
|
||||
assert_equal 2, portfolio_stocks.count
|
||||
|
||||
shares_values = portfolio_stocks.pluck(:shares).sort
|
||||
assert_equal [-4, 10], shares_values
|
||||
|
||||
total_shares = portfolio.shares_owned(stock.id)
|
||||
assert_equal 6, total_shares
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,107 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "test_helper"
|
||||
|
||||
class ImportStudentServiceTest < ActiveSupport::TestCase
|
||||
def setup
|
||||
@classroom = create(:classroom)
|
||||
end
|
||||
|
||||
test "call creates student successfully" do
|
||||
assert_difference("Student.count", 1) do
|
||||
result = ImportStudentService.call(
|
||||
username: "student001",
|
||||
classroom_id: @classroom.id
|
||||
)
|
||||
|
||||
assert result.success?
|
||||
assert_equal :created, result.action
|
||||
assert_equal "student001", result.student.username
|
||||
assert_equal @classroom, result.student.classroom
|
||||
assert_nil result.error_message
|
||||
end
|
||||
end
|
||||
|
||||
test "call skips duplicate username" do
|
||||
create(:student, username: "existing_user", classroom: @classroom)
|
||||
|
||||
assert_no_difference("Student.count") do
|
||||
result = ImportStudentService.call(
|
||||
username: "existing_user",
|
||||
classroom_id: @classroom.id
|
||||
)
|
||||
|
||||
assert result.success?
|
||||
assert_equal :skipped, result.action
|
||||
assert_nil result.student
|
||||
assert_equal "Student with username 'existing_user' already exists", result.error_message
|
||||
end
|
||||
end
|
||||
|
||||
test "call handles invalid classroom_id" do
|
||||
assert_no_difference("Student.count") do
|
||||
result = ImportStudentService.call(
|
||||
username: "student001",
|
||||
classroom_id: "999"
|
||||
)
|
||||
|
||||
assert_not result.success?
|
||||
assert_equal :failed, result.action
|
||||
assert_not_nil result.student
|
||||
assert_not result.student.persisted?
|
||||
assert_match(/Classroom can't be blank/, result.error_message)
|
||||
end
|
||||
end
|
||||
|
||||
test "call handles blank username" do
|
||||
assert_no_difference("Student.count") do
|
||||
result = ImportStudentService.call(
|
||||
username: "",
|
||||
classroom_id: @classroom.id
|
||||
)
|
||||
|
||||
assert result.success?
|
||||
assert_equal :skipped, result.action
|
||||
assert_nil result.student
|
||||
assert_equal "Username is required", result.error_message
|
||||
end
|
||||
end
|
||||
|
||||
test "call handles nil username" do
|
||||
assert_no_difference("Student.count") do
|
||||
result = ImportStudentService.call(
|
||||
username: nil,
|
||||
classroom_id: @classroom.id
|
||||
)
|
||||
|
||||
assert result.success?
|
||||
assert_equal :skipped, result.action
|
||||
assert_nil result.student
|
||||
assert_equal "Username is required", result.error_message
|
||||
end
|
||||
end
|
||||
|
||||
test "call strips whitespace from parameters" do
|
||||
assert_difference("Student.count", 1) do
|
||||
result = ImportStudentService.call(
|
||||
username: " student001 ",
|
||||
classroom_id: " #{@classroom.id} "
|
||||
)
|
||||
|
||||
assert result.success?
|
||||
assert_equal :created, result.action
|
||||
assert_equal "student001", result.student.username
|
||||
end
|
||||
end
|
||||
|
||||
test "call generates password for created student" do
|
||||
result = ImportStudentService.call(
|
||||
username: "student001",
|
||||
classroom_id: @classroom.id
|
||||
)
|
||||
|
||||
assert result.success?
|
||||
assert_not_nil result.student.password
|
||||
assert result.student.password.length.positive?
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,12 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "test_helper"
|
||||
|
||||
class MemorablePasswordGeneratorTest < ActiveSupport::TestCase
|
||||
test "generate returns a password" do
|
||||
password = MemorablePasswordGenerator.generate
|
||||
|
||||
assert password.length.positive?
|
||||
assert password.match(/^\w+[0-9]+\w+$/), "Password should be in the format: [Word][Number][Word]. Got #{password}"
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,87 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "test_helper"
|
||||
|
||||
class StockAttributeUpdateTest < ActiveSupport::TestCase
|
||||
test "updates stock attributes from valid API response" do
|
||||
stock = create(:stock, ticker: "TEST")
|
||||
|
||||
mock_response = {
|
||||
"Symbol" => "TEST",
|
||||
"Name" => "Test Company",
|
||||
"Description" => "A test company description",
|
||||
"Exchange" => "NYSE",
|
||||
"Industry" => "TECHNOLOGY",
|
||||
"OfficialSite" => "https://test.com",
|
||||
"ProfitMargin" => "0.15"
|
||||
}
|
||||
|
||||
Net::HTTP.expects(:get).returns(mock_response.to_json)
|
||||
|
||||
StockAttributeUpdate.execute(stock)
|
||||
stock.reload
|
||||
|
||||
assert_equal "Test Company", stock.company_name
|
||||
assert_equal "A test company description", stock.description
|
||||
assert_equal "NYSE", stock.stock_exchange
|
||||
assert_equal "TECHNOLOGY", stock.industry
|
||||
assert_equal "https://test.com", stock.company_website
|
||||
assert_equal 0.15, stock.profit_margin
|
||||
end
|
||||
|
||||
test "handles invalid API response gracefully" do
|
||||
stock = create(:stock, ticker: "INVALID")
|
||||
|
||||
Net::HTTP.expects(:get).returns("{}")
|
||||
|
||||
original_updated_at = stock.updated_at
|
||||
|
||||
StockAttributeUpdate.execute(stock)
|
||||
stock.reload
|
||||
|
||||
assert_equal original_updated_at, stock.updated_at
|
||||
end
|
||||
|
||||
test "handles API error gracefully" do
|
||||
stock = create(:stock, ticker: "ERROR")
|
||||
|
||||
Net::HTTP.expects(:get).raises(JSON::ParserError.new("Invalid JSON"))
|
||||
|
||||
original_updated_at = stock.updated_at
|
||||
|
||||
StockAttributeUpdate.execute(stock)
|
||||
stock.reload
|
||||
|
||||
assert_equal original_updated_at, stock.updated_at
|
||||
end
|
||||
|
||||
test "skips stock without ticker" do
|
||||
stock = Stock.new(description: "Test stock")
|
||||
stock.save(validate: false)
|
||||
|
||||
Net::HTTP.expects(:get).never
|
||||
|
||||
StockAttributeUpdate.execute(stock)
|
||||
stock.reload
|
||||
|
||||
assert_nil stock.company_name
|
||||
end
|
||||
|
||||
test "handles API response with missing fields" do
|
||||
stock = create(:stock, ticker: "PARTIAL")
|
||||
|
||||
mock_response = {
|
||||
"Symbol" => "PARTIAL",
|
||||
"Name" => "Partial Company"
|
||||
}
|
||||
|
||||
Net::HTTP.expects(:get).returns(mock_response.to_json)
|
||||
|
||||
StockAttributeUpdate.execute(stock)
|
||||
stock.reload
|
||||
|
||||
assert_equal "Partial Company", stock.company_name
|
||||
assert_equal "A sample stock for testing", stock.description
|
||||
assert_equal "NYSE", stock.stock_exchange
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,28 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "test_helper"
|
||||
|
||||
class TransactionFeeProcessorTest < ActiveSupport::TestCase
|
||||
test "it applies 1 fee per student for multiple orders" do
|
||||
user_with_multiple_orders = create(:student)
|
||||
user_with_multiple_orders.portfolio.portfolio_transactions.create!(amount_cents: 100_00, transaction_type: :deposit)
|
||||
user_with_one_order = create(:student)
|
||||
user_with_one_order.portfolio.portfolio_transactions.create!(amount_cents: 100_00, transaction_type: :deposit)
|
||||
|
||||
stock1 = create(:stock, price_cents: 10_00) # $10.00
|
||||
stock2 = create(:stock, price_cents: 20_00) # $20.00
|
||||
|
||||
order1 = create(:order, :pending, user: user_with_multiple_orders, stock: stock1, shares: 1, action: :buy)
|
||||
order2 = create(:order, :pending, user: user_with_multiple_orders, stock: stock2, shares: 1, action: :buy)
|
||||
order3 = create(:order, :pending, user: user_with_one_order, stock: stock1, shares: 1, action: :buy)
|
||||
|
||||
assert_equal 0, user_with_multiple_orders.portfolio.portfolio_transactions.fees.count
|
||||
assert_equal 0, user_with_one_order.portfolio.portfolio_transactions.fees.count
|
||||
|
||||
TransactionFeeProcessor.execute([order1, order2, order3])
|
||||
# Each user should have one transaction fee
|
||||
|
||||
assert_equal 1, user_with_multiple_orders.portfolio.portfolio_transactions.fees.count
|
||||
assert_equal 1, user_with_one_order.portfolio.portfolio_transactions.fees.count
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user