chore: init commit

in worker.../repo/GITFOLDER.zip is the .git folder.
This commit is contained in:
2026-08-11 14:44:09 -04:00
parent 0012380fd3
commit 2854619bc9
782 changed files with 65944 additions and 0 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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