mirror of
https://github.com/we-promise/sure.git
synced 2026-05-30 07:49:01 +00:00
315 lines
10 KiB
Ruby
315 lines
10 KiB
Ruby
require "test_helper"
|
|
|
|
class PdfImportTest < ActiveSupport::TestCase
|
|
include ActiveJob::TestHelper
|
|
|
|
setup do
|
|
@import = imports(:pdf)
|
|
@processed_import = imports(:pdf_processed)
|
|
@import_with_rows = imports(:pdf_with_rows)
|
|
end
|
|
|
|
test "pdf_uploaded? returns false when no file attached" do
|
|
assert_not @import.pdf_uploaded?
|
|
end
|
|
|
|
test "pdf_uploaded? returns true for statement backed import" do
|
|
statement = create_pdf_statement
|
|
import = PdfImport.create_from_statement!(statement: statement)
|
|
|
|
assert import.pdf_uploaded?
|
|
assert import.statement_backed?
|
|
assert_equal statement.original_file.download, import.pdf_file_content
|
|
assert_equal statement.filename, import.pdf_filename
|
|
end
|
|
|
|
test "ai_processed? returns false when no summary present" do
|
|
assert_not @import.ai_processed?
|
|
end
|
|
|
|
test "ai_processed? returns true when summary present" do
|
|
assert @processed_import.ai_processed?
|
|
end
|
|
|
|
test "uploaded? delegates to pdf_uploaded?" do
|
|
assert_not @import.uploaded?
|
|
end
|
|
|
|
test "configured? requires AI processed and rows" do
|
|
assert_not @import.configured?
|
|
assert_not @processed_import.configured?
|
|
assert @import_with_rows.configured?
|
|
end
|
|
|
|
test "cleaned? requires configured and valid rows" do
|
|
assert_not @import.cleaned?
|
|
assert_not @processed_import.cleaned?
|
|
end
|
|
|
|
test "publishable? requires bank statement with cleaned rows and valid mappings" do
|
|
assert_not @import.publishable?
|
|
assert_not @processed_import.publishable?
|
|
end
|
|
|
|
test "status detail cleaned check requires account and transaction statement" do
|
|
@import_with_rows.update!(account: accounts(:depository), document_type: "bank_statement")
|
|
|
|
assert @import_with_rows.cleaned_from_validation_stats?(invalid_rows_count: 0)
|
|
assert_not @import_with_rows.cleaned_from_validation_stats?(invalid_rows_count: 1)
|
|
|
|
@import_with_rows.update!(account: nil)
|
|
assert_not @import_with_rows.cleaned_from_validation_stats?(invalid_rows_count: 0)
|
|
|
|
@import_with_rows.update!(account: accounts(:depository), document_type: "other")
|
|
assert_not @import_with_rows.cleaned_from_validation_stats?(invalid_rows_count: 0)
|
|
end
|
|
|
|
test "column_keys returns transaction columns" do
|
|
assert_equal %i[date amount name category notes], @import.column_keys
|
|
end
|
|
|
|
test "required_column_keys returns date and amount" do
|
|
assert_equal %i[date amount], @import.required_column_keys
|
|
end
|
|
|
|
test "document_type validates against allowed types" do
|
|
@import.document_type = "bank_statement"
|
|
assert @import.valid?
|
|
|
|
@import.document_type = "invalid_type"
|
|
assert_not @import.valid?
|
|
assert @import.errors[:document_type].present?
|
|
end
|
|
|
|
test "document_type allows nil" do
|
|
@import.document_type = nil
|
|
assert @import.valid?
|
|
end
|
|
|
|
test "process_with_ai_later enqueues ProcessPdfJob" do
|
|
import = PdfImport.create_from_statement!(statement: create_pdf_statement)
|
|
|
|
assert_enqueued_with job: ProcessPdfJob, args: [ import ] do
|
|
assert import.process_with_ai_later
|
|
end
|
|
|
|
assert_equal "importing", import.reload.status
|
|
end
|
|
|
|
test "process_with_ai_later does not enqueue duplicate jobs while importing" do
|
|
import = PdfImport.create_from_statement!(statement: create_pdf_statement)
|
|
|
|
assert_enqueued_jobs 1, only: ProcessPdfJob do
|
|
assert import.process_with_ai_later
|
|
assert_not import.reload.process_with_ai_later
|
|
end
|
|
|
|
assert_equal "importing", import.reload.status
|
|
end
|
|
|
|
test "process_with_ai_later does not claim import without pdf content" do
|
|
assert_no_enqueued_jobs only: ProcessPdfJob do
|
|
assert_not @import.process_with_ai_later
|
|
end
|
|
|
|
assert_equal "pending", @import.reload.status
|
|
end
|
|
|
|
test "process_with_ai_later resets pending when enqueue fails" do
|
|
import = PdfImport.create_from_statement!(statement: create_pdf_statement)
|
|
ProcessPdfJob.stubs(:perform_later).raises(StandardError, "queue offline")
|
|
|
|
assert_not import.process_with_ai_later
|
|
assert_equal "pending", import.reload.status
|
|
end
|
|
|
|
test "generate_rows_from_extracted_data creates import rows" do
|
|
import = imports(:pdf_with_rows)
|
|
import.rows.destroy_all
|
|
import.update_column(:rows_count, 0)
|
|
|
|
import.generate_rows_from_extracted_data
|
|
|
|
assert_equal 2, import.rows.count
|
|
assert_equal 2, import.rows_count
|
|
|
|
coffee_row = import.rows.find_by(name: "Coffee Shop")
|
|
assert_not_nil coffee_row
|
|
assert_equal "-50.0", coffee_row.amount
|
|
assert_equal "Food & Drink", coffee_row.category
|
|
|
|
salary_row = import.rows.find_by(name: "Salary")
|
|
assert_not_nil salary_row
|
|
assert_equal "1500.0", salary_row.amount
|
|
end
|
|
|
|
test "generate_rows_from_extracted_data does nothing without extracted transactions" do
|
|
@import.generate_rows_from_extracted_data
|
|
assert_equal 0, @import.rows.count
|
|
end
|
|
|
|
test "extracted_transactions returns transactions from extracted_data" do
|
|
assert_equal 2, @import_with_rows.extracted_transactions.size
|
|
assert_equal "Coffee Shop", @import_with_rows.extracted_transactions.first["name"]
|
|
end
|
|
|
|
test "extracted_transactions returns empty array when no data" do
|
|
assert_equal [], @import.extracted_transactions
|
|
end
|
|
|
|
test "has_extracted_transactions? returns true with transactions" do
|
|
assert @import_with_rows.has_extracted_transactions?
|
|
end
|
|
|
|
test "has_extracted_transactions? returns false without transactions" do
|
|
assert_not @import.has_extracted_transactions?
|
|
end
|
|
|
|
test "mapping_steps is empty when no categories in rows" do
|
|
# PDF imports use direct account selection in UI, not AccountMapping
|
|
assert_equal [], @import.mapping_steps
|
|
end
|
|
|
|
test "mapping_steps includes CategoryMapping when rows have categories" do
|
|
@import_with_rows.rows.create!(
|
|
source_row_number: 1,
|
|
date: "01/15/2024",
|
|
amount: -50.00,
|
|
currency: "USD",
|
|
name: "Test Transaction",
|
|
category: "Groceries"
|
|
)
|
|
assert_equal [ Import::CategoryMapping ], @import_with_rows.mapping_steps
|
|
end
|
|
|
|
test "mapping_steps does not include AccountMapping even when account is nil" do
|
|
# PDF imports handle account selection via direct UI, not mapping system
|
|
assert_nil @import.account
|
|
assert_not_includes @import.mapping_steps, Import::AccountMapping
|
|
end
|
|
|
|
test "destroying import purges attached pdf_file" do
|
|
@import.pdf_file.attach(
|
|
io: StringIO.new("fake-pdf-content"),
|
|
filename: "statement.pdf",
|
|
content_type: "application/pdf"
|
|
)
|
|
|
|
attachment_id = @import.pdf_file.id
|
|
assert ActiveStorage::Attachment.exists?(attachment_id)
|
|
|
|
perform_enqueued_jobs do
|
|
@import.destroy!
|
|
end
|
|
|
|
assert_not ActiveStorage::Attachment.exists?(attachment_id)
|
|
end
|
|
|
|
test "destroying statement backed import keeps statement file" do
|
|
statement = create_pdf_statement
|
|
import = PdfImport.create_from_statement!(statement: statement)
|
|
attachment_id = statement.original_file.id
|
|
|
|
perform_enqueued_jobs do
|
|
import.destroy!
|
|
end
|
|
|
|
assert ActiveStorage::Attachment.exists?(attachment_id)
|
|
end
|
|
|
|
test "statement backed import prevents source statement destroy" do
|
|
statement = create_pdf_statement
|
|
import = PdfImport.create_from_statement!(statement: statement)
|
|
|
|
assert_no_difference "AccountStatement.count" do
|
|
assert_not statement.destroy
|
|
end
|
|
|
|
assert_equal statement, import.reload.account_statement
|
|
end
|
|
|
|
test "statement backed import memoizes pdf content" do
|
|
statement = create_pdf_statement
|
|
import = PdfImport.create_from_statement!(statement: statement)
|
|
statement.original_file.expects(:download).once.returns("%PDF-test")
|
|
|
|
assert_equal "%PDF-test", import.pdf_file_content
|
|
assert_equal "%PDF-test", import.pdf_file_content
|
|
end
|
|
|
|
test "statement backed import reuse requires current account and date format" do
|
|
statement = create_pdf_statement
|
|
stale_import = PdfImport.create_from_statement!(statement: statement)
|
|
formats = Family::DATE_FORMATS.map(&:last)
|
|
alternate_date_format = (formats - [ statement.family.date_format ]).first || "#{statement.family.date_format}-alternate"
|
|
stale_import.update!(account: nil, date_format: alternate_date_format)
|
|
|
|
fresh_import = PdfImport.create_from_statement!(statement: statement)
|
|
|
|
assert_not_equal stale_import, fresh_import
|
|
assert_equal statement.account, fresh_import.account
|
|
assert_equal statement.family.date_format, fresh_import.date_format
|
|
end
|
|
|
|
test "statement backed import reuses matching reusable import" do
|
|
statement = create_pdf_statement
|
|
existing_import = PdfImport.create_from_statement!(statement: statement)
|
|
|
|
assert_equal existing_import, PdfImport.create_from_statement!(statement: statement)
|
|
end
|
|
|
|
test "assigning account links statement backed import statement" do
|
|
statement = create_pdf_statement(account: nil)
|
|
import = PdfImport.create_from_statement!(statement: statement)
|
|
account = accounts(:depository)
|
|
|
|
import.assign_account!(account)
|
|
|
|
assert_equal account, import.reload.account
|
|
assert_equal account, statement.reload.account
|
|
assert statement.linked?
|
|
end
|
|
|
|
test "statement backed import requires pdf statement" do
|
|
csv_statement = AccountStatement.create_from_upload!(
|
|
family: @import.family,
|
|
account: nil,
|
|
file: uploaded_file(filename: "statement.csv", content_type: "text/csv", content: "date,amount\n2024-01-01,1\n")
|
|
)
|
|
import = PdfImport.new(family: @import.family, account_statement: csv_statement)
|
|
|
|
assert_not import.valid?
|
|
assert import.errors[:account_statement].present?
|
|
end
|
|
|
|
test "statement backed import requires statement from same family" do
|
|
statement = AccountStatement.create_from_upload!(
|
|
family: families(:empty),
|
|
account: nil,
|
|
file: uploaded_file(
|
|
filename: "other_family_statement.pdf",
|
|
content_type: "application/pdf",
|
|
content: file_fixture("imports/sample_bank_statement.pdf").binread
|
|
)
|
|
)
|
|
import = PdfImport.new(family: @import.family, account_statement: statement)
|
|
|
|
assert_not import.valid?
|
|
assert import.errors[:account_statement].present?
|
|
end
|
|
|
|
private
|
|
|
|
def create_pdf_statement(account: accounts(:depository))
|
|
AccountStatement.create_from_upload!(
|
|
family: @import.family,
|
|
account: account,
|
|
file: uploaded_file(
|
|
filename: "sample_bank_statement.pdf",
|
|
content_type: "application/pdf",
|
|
content: file_fixture("imports/sample_bank_statement.pdf").binread
|
|
)
|
|
)
|
|
end
|
|
end
|