From 9e6fbc1374142833ec220e57cacda9be7afc2111 Mon Sep 17 00:00:00 2001 From: ghost <49853598+JSONbored@users.noreply.github.com> Date: Mon, 11 May 2026 15:02:15 -0700 Subject: [PATCH] test(recurring): prove export roundtrip semantics (#1727) * test(recurring): prove export roundtrip semantics * test(recurring): assert export roundtrip payload --- test/models/family/data_importer_test.rb | 79 ++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/test/models/family/data_importer_test.rb b/test/models/family/data_importer_test.rb index b216acc9d..22691648c 100644 --- a/test/models/family/data_importer_test.rb +++ b/test/models/family/data_importer_test.rb @@ -313,6 +313,85 @@ class Family::DataImporterTest < ActiveSupport::TestCase assert_equal(-89.99, recurring_transaction.expected_amount_avg.to_f) end + test "round trips recurring transaction export semantics" do + source_family = Family.create!(name: "Recurring Source", currency: "USD") + source_account = source_family.accounts.create!( + name: "Source Checking", + accountable: Depository.new, + balance: 1000, + currency: "USD" + ) + source_merchant = source_family.merchants.create!(name: "Internet Provider") + + source_family.recurring_transactions.create!( + account: source_account, + merchant: source_merchant, + amount: -89.99, + currency: "USD", + expected_day_of_month: 14, + last_occurrence_date: Date.parse("2024-01-14"), + next_expected_date: Date.parse("2024-02-14"), + status: "active", + occurrence_count: 6, + manual: true, + expected_amount_min: -95, + expected_amount_max: -85, + expected_amount_avg: -89.99 + ) + + source_family.recurring_transactions.create!( + name: "Quarterly Insurance", + amount: 240, + currency: "USD", + expected_day_of_month: 28, + last_occurrence_date: Date.parse("2024-01-28"), + next_expected_date: Date.parse("2024-04-28"), + status: "inactive", + occurrence_count: 2, + manual: false + ) + + ndjson = nil + Zip::File.open_buffer(Family::DataExporter.new(source_family).generate_export) do |zip| + ndjson = zip.read("all.ndjson") + end + + assert_not_nil ndjson + assert ndjson.include?('"type":"RecurringTransaction"') + + Family::DataImporter.new(@family, ndjson).import! + + assert_equal 2, @family.recurring_transactions.count + + restored_account = @family.accounts.find_by!(name: "Source Checking") + restored_merchant = @family.merchants.find_by!(name: "Internet Provider") + restored_provider = @family.recurring_transactions.find_by!(merchant: restored_merchant) + + assert_equal restored_account, restored_provider.account + assert_equal(-89.99, restored_provider.amount.to_f) + assert_equal "USD", restored_provider.currency + assert_equal 14, restored_provider.expected_day_of_month + assert_equal Date.parse("2024-01-14"), restored_provider.last_occurrence_date + assert_equal Date.parse("2024-02-14"), restored_provider.next_expected_date + assert_equal "active", restored_provider.status + assert_equal 6, restored_provider.occurrence_count + assert_equal true, restored_provider.manual + assert_equal(-95.0, restored_provider.expected_amount_min.to_f) + assert_equal(-85.0, restored_provider.expected_amount_max.to_f) + assert_equal(-89.99, restored_provider.expected_amount_avg.to_f) + + restored_named = @family.recurring_transactions.find_by!(name: "Quarterly Insurance") + assert_nil restored_named.account + assert_nil restored_named.merchant + assert_equal 240.0, restored_named.amount.to_f + assert_equal 28, restored_named.expected_day_of_month + assert_equal Date.parse("2024-01-28"), restored_named.last_occurrence_date + assert_equal Date.parse("2024-04-28"), restored_named.next_expected_date + assert_equal "inactive", restored_named.status + assert_equal 2, restored_named.occurrence_count + assert_equal false, restored_named.manual + end + test "imports recurring transactions with unknown status fallback" do ndjson = build_ndjson([ {