Files
sure/test/models/pdf_import_test.rb
Juan José Mata 0fb9d60ee6 Use dependent: :purge_later for ActiveRecord attachments (#882)
* Use dependent: :purge_later for user profile_image cleanup

This is a simpler alternative to PR #787's callback-based approach.
Instead of adding a custom callback and method, we use Rails' built-in
`dependent: :purge_later` option which is already used by FamilyExport
and other models in the codebase.

This single-line change ensures orphaned ActiveStorage attachments are
automatically purged when a user is destroyed, without the overhead of
querying all attachments manually.

https://claude.ai/code/session_01Np3deHEAJqCBfz3aY7c3Tk

* Add dependent: :purge_later to all ActiveStorage attachments

Extends the attachment cleanup from PR #787 to cover ALL models with
ActiveStorage attachments, not just User.profile_image.

Models updated:
- PdfImport.pdf_file - prevents orphaned PDF files from imports
- Account.logo - prevents orphaned account logos
- PlaidItem.logo, SimplefinItem.logo, SnaptradeItem.logo,
  CoinstatsItem.logo, CoinbaseItem.logo, LunchflowItem.logo,
  MercuryItem.logo, EnableBankingItem.logo - prevents orphaned
  provider logos

This ensures that when a family is deleted (cascade from last user
purge), all associated storage files are properly cleaned up via
Rails' built-in dependent: :purge_later mechanism.

https://claude.ai/code/session_01Np3deHEAJqCBfz3aY7c3Tk

* Make sure `Provider` generator adds it

* Fix tests

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-03 15:45:25 +01:00

153 lines
4.5 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 "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 "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
assert_enqueued_with job: ProcessPdfJob, args: [ @import ] do
@import.process_with_ai_later
end
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!(
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
end