Files
sure/test/models/sure_import_test.rb
Juan José Mata 2595885eb7 Full .ndjson import / reorganize UI with Financial Tools / Raw Data tabs (#1208)
* Reorganize import UI with Financial Tools / Raw Data tabs

Split the flat list of import sources into two tabbed sections using
DS::Tabs: "Financial Tools" (Mint, Quicken/QIF, YNAB coming soon) and
"Raw Data" (transactions, investments, accounts, categories, rules,
documents). This prepares for adding more tool-specific importers
without cluttering the list.

https://claude.ai/code/session_01BM4SBWNhATqoKTEvy3qTS3

* Fix import controller test to account for YNAB coming soon entry

The new YNAB "coming soon" disabled entry adds a 5th aria-disabled
element to the import dialog.

https://claude.ai/code/session_01BM4SBWNhATqoKTEvy3qTS3

* Fix system tests to click Raw Data tab before selecting import type

Transaction, trade, and account imports are now under the Raw Data tab
and need an explicit tab click before the buttons are visible.

https://claude.ai/code/session_01BM4SBWNhATqoKTEvy3qTS3

* feat: Add bulk import for NDJSON export files

Implements an import flow that accepts the full all.ndjson file from data exports,
allowing users to restore their complete data including:
- Accounts with accountable types
- Categories with parent relationships
- Tags and merchants
- Transactions with category, merchant, and tag references
- Trades with securities
- Valuations
- Budgets and budget categories
- Rules with conditions and actions (including compound conditions)

Key changes:
- Add BulkImport model extending Import base class
- Add Family::DataImporter to handle NDJSON parsing and import logic
- Update imports controller and views to support NDJSON workflow
- Skip configuration/mapping steps for structured NDJSON imports
- Add i18n translations for bulk import UI
- Add tests for BulkImport and DataImporter

* fix: Fix category import and test query issues

- Add default lucide_icon ("shapes") for categories when not provided
- Fix valuation test to use proper ActiveRecord joins syntax

* Linter errors

* fix: Add default color for tags when not provided in import

* fix: Add default kind for transactions when not provided in import

* Fix test

* Fix tests

* Fix remaining merge conflicts from PR 766 cherry-pick

Resolve conflict markers in test fixtures and clean up BulkImport
entry in new.html.erb to use the _import_option partial consistently.

https://claude.ai/code/session_01BM4SBWNhATqoKTEvy3qTS3

* Import Sure `.ndjson`

* Remove `.ndjson` import from raw data

* Fix support for Sure "bulk" import from old branch

* Linter

* Fix CI test

* Fix more CI tests

* Fix tests

* Fix tests / move PDF import to first tab

* Remove redundant title

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-23 14:27:41 +01:00

188 lines
5.1 KiB
Ruby

require "test_helper"
class SureImportTest < ActiveSupport::TestCase
include ActiveJob::TestHelper
setup do
@family = families(:dylan_family)
@import = @family.imports.create!(type: "SureImport")
end
test "dry_run reflects attached ndjson content" do
ndjson = [
{ type: "Account", data: { id: "uuid-1", name: "Test", balance: "1000", currency: "USD", accountable_type: "Depository" } },
{ type: "Transaction", data: { id: "uuid-2" } }
].map(&:to_json).join("\n")
attach_ndjson(ndjson)
dry_run = @import.dry_run
assert_equal 1, dry_run[:accounts]
assert_equal 1, dry_run[:transactions]
end
test "publishable? is false when attached file has no supported records" do
ndjson = { type: "UnknownType", data: {} }.to_json
attach_ndjson(ndjson)
assert @import.uploaded?
assert_not @import.publishable?
end
test "column_keys required_column_keys and mapping_steps are empty" do
assert_equal [], @import.column_keys
assert_equal [], @import.required_column_keys
assert_equal [], @import.mapping_steps
end
test "max_row_count is higher than standard imports" do
assert_equal 100_000, @import.max_row_count
end
test "csv_template returns nil" do
assert_nil @import.csv_template
end
test "uploaded? returns false without ndjson attachment" do
assert_not @import.uploaded?
end
test "uploaded? returns true with valid ndjson attachment" do
attach_ndjson(build_ndjson([
{ type: "Account", data: { id: "uuid-1", name: "Test", balance: "1000", currency: "USD", accountable_type: "Depository" } }
]))
assert @import.uploaded?
end
test "uploaded? returns false with invalid ndjson attachment" do
attach_ndjson("not valid json")
assert_not @import.uploaded?
end
test "configured? and cleaned? follow uploaded?" do
attach_ndjson(build_ndjson([
{ type: "Account", data: { id: "uuid-1", name: "Test", balance: "1000", currency: "USD", accountable_type: "Depository" } }
]))
assert @import.configured?
assert @import.cleaned?
end
test "publishable? returns true when uploaded and valid" do
attach_ndjson(build_ndjson([
{ type: "Account", data: { id: "uuid-1", name: "Test", balance: "1000", currency: "USD", accountable_type: "Depository" } }
]))
assert @import.publishable?
end
test "dry_run returns counts by type" do
attach_ndjson(build_ndjson([
{ type: "Account", data: { id: "uuid-1" } },
{ type: "Account", data: { id: "uuid-2" } },
{ type: "Category", data: { id: "uuid-3" } },
{ type: "Transaction", data: { id: "uuid-4" } },
{ type: "Transaction", data: { id: "uuid-5" } },
{ type: "Transaction", data: { id: "uuid-6" } }
]))
dry_run = @import.dry_run
assert_equal 2, dry_run[:accounts]
assert_equal 1, dry_run[:categories]
assert_equal 3, dry_run[:transactions]
assert_equal 0, dry_run[:tags]
end
test "sync_ndjson_rows_count! sets total row count" do
attach_ndjson(build_ndjson([
{ type: "Account", data: { id: "uuid-1" } },
{ type: "Category", data: { id: "uuid-2" } },
{ type: "Transaction", data: { id: "uuid-3" } }
]))
@import.sync_ndjson_rows_count!
assert_equal 3, @import.rows_count
end
test "publishes import successfully" do
attach_ndjson(build_ndjson([
{ type: "Account", data: {
id: "uuid-1",
name: "Import Test Account",
balance: "1000.00",
currency: "USD",
accountable_type: "Depository",
accountable: { subtype: "checking" }
} }
]))
initial_account_count = @family.accounts.count
@import.publish
assert_equal "complete", @import.status
assert_equal initial_account_count + 1, @family.accounts.count
account = @family.accounts.find_by(name: "Import Test Account")
assert_not_nil account
assert_equal 1000.0, account.balance.to_f
assert_equal "USD", account.currency
assert_equal "Depository", account.accountable_type
end
test "import tracks created accounts for revert" do
attach_ndjson(build_ndjson([
{ type: "Account", data: {
id: "uuid-1",
name: "Revertable Account",
balance: "500.00",
currency: "USD",
accountable_type: "Depository"
} }
]))
@import.publish
assert_equal 1, @import.accounts.count
assert_equal "Revertable Account", @import.accounts.first.name
end
test "publishes later enqueues job" do
attach_ndjson(build_ndjson([
{ type: "Account", data: {
id: "uuid-1",
name: "Async Account",
balance: "100",
currency: "USD",
accountable_type: "Depository"
} }
]))
assert_enqueued_with job: ImportJob, args: [ @import ] do
@import.publish_later
end
assert_equal "importing", @import.status
end
private
def attach_ndjson(ndjson)
@import.ndjson_file.attach(
io: StringIO.new(ndjson),
filename: "all.ndjson",
content_type: "application/x-ndjson"
)
@import.sync_ndjson_rows_count!
end
def build_ndjson(records)
records.map(&:to_json).join("\n")
end
end