@actual-app/core 26.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.swcrc +11 -0
- package/bin/build-browser +40 -0
- package/bin/copy-migrations +9 -0
- package/db.sqlite +0 -0
- package/default-db.sqlite +0 -0
- package/migrations/.force-copy-windows +0 -0
- package/migrations/1548957970627_remove-db-version.sql +5 -0
- package/migrations/1550601598648_payees.sql +23 -0
- package/migrations/1555786194328_remove_category_group_unique.sql +25 -0
- package/migrations/1561751833510_indexes.sql +7 -0
- package/migrations/1567699552727_budget.sql +38 -0
- package/migrations/1582384163573_cleared.sql +6 -0
- package/migrations/1597756566448_rules.sql +10 -0
- package/migrations/1608652596043_parent_field.sql +13 -0
- package/migrations/1608652596044_trans_views.sql +56 -0
- package/migrations/1612625548236_optimize.sql +7 -0
- package/migrations/1614782639336_trans_views2.sql +33 -0
- package/migrations/1615745967948_meta.sql +10 -0
- package/migrations/1616167010796_accounts_order.sql +5 -0
- package/migrations/1618975177358_schedules.sql +28 -0
- package/migrations/1632571489012_remove_cache.js +136 -0
- package/migrations/1679728867040_rules_conditions.sql +5 -0
- package/migrations/1681115033845_add_schedule_name.sql +5 -0
- package/migrations/1682974838138_remove_payee_rules.sql +5 -0
- package/migrations/1685007876842_add_category_hidden.sql +6 -0
- package/migrations/1686139660866_remove_account_type.sql +5 -0
- package/migrations/1688749527273_transaction_filters.sql +10 -0
- package/migrations/1688841238000_add_account_type.sql +5 -0
- package/migrations/1691233396000_add_schedule_next_date_tombstone.sql +5 -0
- package/migrations/1694438752000_add_goal_targets.sql +7 -0
- package/migrations/1697046240000_add_reconciled.sql +5 -0
- package/migrations/1704572023730_add_account_sync_source.sql +5 -0
- package/migrations/1704572023731_add_missing_goCardless_sync_source.sql +9 -0
- package/migrations/1707267033000_reports.sql +28 -0
- package/migrations/1712784523000_unhide_input_group.sql +8 -0
- package/migrations/1716359441000_include_current.sql +5 -0
- package/migrations/1720310586000_link_transfer_schedules.sql +19 -0
- package/migrations/1720664867241_add_payee_favorite.sql +5 -0
- package/migrations/1720665000000_goal_context.sql +6 -0
- package/migrations/1722717601000_reports_move_selected_categories.js +55 -0
- package/migrations/1722804019000_create_dashboard_table.js +69 -0
- package/migrations/1723665565000_prefs.js +59 -0
- package/migrations/1730744182000_fix_dashboard_table.sql +7 -0
- package/migrations/1736640000000_custom_report_sorting.sql +7 -0
- package/migrations/1737158400000_add_learn_categories_to_payees.sql +5 -0
- package/migrations/1738491452000_sorting_rename.sql +13 -0
- package/migrations/1739139550000_bank_sync_page.sql +7 -0
- package/migrations/1740506588539_add_last_reconciled_at.sql +5 -0
- package/migrations/1745425408000_update_budgetType_pref.sql +7 -0
- package/migrations/1749799110000_add_tags.sql +10 -0
- package/migrations/1749799110001_tags_tombstone.sql +5 -0
- package/migrations/1754611200000_add_category_template_settings.sql +5 -0
- package/migrations/1759260219000_add_trim_interval_report_setting.sql +6 -0
- package/migrations/1759842823172_add_isGlobal_to_preferences.sql +1 -0
- package/migrations/1762178745667_rename_csv_skip_lines_pref.sql +8 -0
- package/migrations/1765518577215_multiple_dashboards.js +30 -0
- package/migrations/1768872504000_add_payee_locations.sql +21 -0
- package/package.json +128 -0
- package/src/mocks/arbitrary-schema.ts +162 -0
- package/src/mocks/budget.ts +901 -0
- package/src/mocks/files/8859-1.qfx +63 -0
- package/src/mocks/files/best.data-ever$.QFX +124 -0
- package/src/mocks/files/big.data.QiF +91 -0
- package/src/mocks/files/budgets/.commit-to-git +0 -0
- package/src/mocks/files/camt/camt.053.payee-memo.xml +127 -0
- package/src/mocks/files/camt/camt.053.xml +463 -0
- package/src/mocks/files/credit-card.ofx +11 -0
- package/src/mocks/files/data-multi-decimal.ofx +64 -0
- package/src/mocks/files/data-payee-memo.ofx +75 -0
- package/src/mocks/files/data-payee-memo.qif +17 -0
- package/src/mocks/files/data.ofx +124 -0
- package/src/mocks/files/data.qfx +124 -0
- package/src/mocks/files/data.qif +91 -0
- package/src/mocks/files/default-budget-template/db.sqlite +0 -0
- package/src/mocks/files/default-budget-template/metadata.json +6 -0
- package/src/mocks/files/html-vals.qfx +17 -0
- package/src/mocks/index.ts +221 -0
- package/src/mocks/migrations/1508717984291_up_add-poop.sql +13 -0
- package/src/mocks/migrations/1508718036311_up_modify-poop.sql +2 -0
- package/src/mocks/migrations/1508727787513_remove-is_income.sql +15 -0
- package/src/mocks/random.ts +16 -0
- package/src/mocks/setup.ts +180 -0
- package/src/mocks/spreadsheet.ts +101 -0
- package/src/mocks/util.ts +82 -0
- package/src/platform/client/connection/README.md +3 -0
- package/src/platform/client/connection/__mocks__/index.ts +67 -0
- package/src/platform/client/connection/index-types.ts +95 -0
- package/src/platform/client/connection/index.browser.ts +213 -0
- package/src/platform/client/connection/index.ts +155 -0
- package/src/platform/client/undo/index.ts +59 -0
- package/src/platform/exceptions/__mocks__/index.ts +7 -0
- package/src/platform/exceptions/index.ts +9 -0
- package/src/platform/server/asyncStorage/__mocks__/index.ts +50 -0
- package/src/platform/server/asyncStorage/index-types.ts +35 -0
- package/src/platform/server/asyncStorage/index.api.ts +2 -0
- package/src/platform/server/asyncStorage/index.electron.ts +88 -0
- package/src/platform/server/asyncStorage/index.ts +126 -0
- package/src/platform/server/connection/README.md +3 -0
- package/src/platform/server/connection/__mocks__/index.ts +15 -0
- package/src/platform/server/connection/index-types.ts +20 -0
- package/src/platform/server/connection/index.api.ts +13 -0
- package/src/platform/server/connection/index.electron.ts +102 -0
- package/src/platform/server/connection/index.ts +154 -0
- package/src/platform/server/fetch/__mocks__/index.ts +3 -0
- package/src/platform/server/fetch/index.api.ts +1 -0
- package/src/platform/server/fetch/index.electron.ts +18 -0
- package/src/platform/server/fetch/index.ts +20 -0
- package/src/platform/server/fs/index.api.ts +198 -0
- package/src/platform/server/fs/index.electron.ts +208 -0
- package/src/platform/server/fs/index.test.ts +117 -0
- package/src/platform/server/fs/index.ts +416 -0
- package/src/platform/server/fs/path-join.api.ts +1 -0
- package/src/platform/server/fs/path-join.electron.ts +1 -0
- package/src/platform/server/fs/path-join.ts +97 -0
- package/src/platform/server/fs/shared.ts +33 -0
- package/src/platform/server/indexeddb/index.ts +115 -0
- package/src/platform/server/log/index.ts +43 -0
- package/src/platform/server/sqlite/index.api.ts +2 -0
- package/src/platform/server/sqlite/index.electron.ts +134 -0
- package/src/platform/server/sqlite/index.test.ts +108 -0
- package/src/platform/server/sqlite/index.ts +241 -0
- package/src/platform/server/sqlite/normalise.ts +9 -0
- package/src/platform/server/sqlite/unicodeLike.test.ts +58 -0
- package/src/platform/server/sqlite/unicodeLike.ts +31 -0
- package/src/server/__mocks__/post.ts +9 -0
- package/src/server/__snapshots__/main.test.ts.snap +199 -0
- package/src/server/__snapshots__/sheet.test.ts.snap +9 -0
- package/src/server/accounts/__snapshots__/sync.test.ts.snap +136 -0
- package/src/server/accounts/app-bank-sync.test.ts +136 -0
- package/src/server/accounts/app.ts +1294 -0
- package/src/server/accounts/link.ts +25 -0
- package/src/server/accounts/payees.ts +36 -0
- package/src/server/accounts/sync.test.ts +679 -0
- package/src/server/accounts/sync.ts +1168 -0
- package/src/server/accounts/title/index.ts +60 -0
- package/src/server/accounts/title/lower-case.ts +93 -0
- package/src/server/accounts/title/specials.ts +21 -0
- package/src/server/admin/app.ts +241 -0
- package/src/server/api-models.ts +244 -0
- package/src/server/api.test.ts +36 -0
- package/src/server/api.ts +1030 -0
- package/src/server/app.ts +91 -0
- package/src/server/aql/compiler.test.ts +966 -0
- package/src/server/aql/compiler.ts +1222 -0
- package/src/server/aql/exec.test.ts +289 -0
- package/src/server/aql/exec.ts +128 -0
- package/src/server/aql/index.ts +41 -0
- package/src/server/aql/schema/executors.test.ts +420 -0
- package/src/server/aql/schema/executors.ts +345 -0
- package/src/server/aql/schema/index.test.ts +67 -0
- package/src/server/aql/schema/index.ts +409 -0
- package/src/server/aql/schema-helpers.test.ts +242 -0
- package/src/server/aql/schema-helpers.ts +208 -0
- package/src/server/aql/views.test.ts +62 -0
- package/src/server/aql/views.ts +57 -0
- package/src/server/auth/app.ts +387 -0
- package/src/server/bench.ts +29 -0
- package/src/server/budget/actions.ts +686 -0
- package/src/server/budget/app.ts +469 -0
- package/src/server/budget/base.test.ts +340 -0
- package/src/server/budget/base.ts +339 -0
- package/src/server/budget/category-template-context.test.ts +1658 -0
- package/src/server/budget/category-template-context.ts +862 -0
- package/src/server/budget/cleanup-template.pegjs +27 -0
- package/src/server/budget/cleanup-template.ts +408 -0
- package/src/server/budget/envelope.ts +403 -0
- package/src/server/budget/goal-template.pegjs +110 -0
- package/src/server/budget/goal-template.ts +309 -0
- package/src/server/budget/report.ts +308 -0
- package/src/server/budget/schedule-template.test.ts +184 -0
- package/src/server/budget/schedule-template.ts +351 -0
- package/src/server/budget/statements.ts +60 -0
- package/src/server/budget/template-notes.test.ts +393 -0
- package/src/server/budget/template-notes.ts +323 -0
- package/src/server/budget/util.ts +25 -0
- package/src/server/budgetfiles/__snapshots__/backups.test.ts.snap +101 -0
- package/src/server/budgetfiles/app.ts +672 -0
- package/src/server/budgetfiles/backups.test.ts +79 -0
- package/src/server/budgetfiles/backups.ts +251 -0
- package/src/server/cloud-storage.ts +467 -0
- package/src/server/dashboard/app.ts +373 -0
- package/src/server/db/__snapshots__/index.test.ts.snap +271 -0
- package/src/server/db/index.test.ts +300 -0
- package/src/server/db/index.ts +855 -0
- package/src/server/db/mappings.ts +59 -0
- package/src/server/db/sort.ts +58 -0
- package/src/server/db/types/index.ts +342 -0
- package/src/server/db/util.ts +36 -0
- package/src/server/encryption/app.ts +133 -0
- package/src/server/encryption/encryption-internals.api.ts +2 -0
- package/src/server/encryption/encryption-internals.electron.ts +89 -0
- package/src/server/encryption/encryption-internals.ts +109 -0
- package/src/server/encryption/encryption.test.ts +19 -0
- package/src/server/encryption/index.test.ts +19 -0
- package/src/server/encryption/index.ts +89 -0
- package/src/server/errors.ts +110 -0
- package/src/server/filters/app.ts +191 -0
- package/src/server/importers/actual.ts +49 -0
- package/src/server/importers/index.ts +58 -0
- package/src/server/importers/ynab4-types.ts +163 -0
- package/src/server/importers/ynab4.ts +470 -0
- package/src/server/importers/ynab5-types.ts +290 -0
- package/src/server/importers/ynab5.ts +1193 -0
- package/src/server/main-app.ts +25 -0
- package/src/server/main.test.ts +392 -0
- package/src/server/main.ts +336 -0
- package/src/server/migrate/__snapshots__/migrations.test.ts.snap +17 -0
- package/src/server/migrate/cli.ts +100 -0
- package/src/server/migrate/migrations.test.ts +81 -0
- package/src/server/migrate/migrations.ts +192 -0
- package/src/server/models.ts +184 -0
- package/src/server/mutators.ts +139 -0
- package/src/server/notes/app.ts +18 -0
- package/src/server/payees/app.ts +351 -0
- package/src/server/polyfills.ts +26 -0
- package/src/server/post.ts +219 -0
- package/src/server/preferences/app.ts +249 -0
- package/src/server/prefs.ts +91 -0
- package/src/server/reports/app.ts +187 -0
- package/src/server/rules/action.ts +344 -0
- package/src/server/rules/app.ts +193 -0
- package/src/server/rules/condition.ts +436 -0
- package/src/server/rules/customFunctions.ts +61 -0
- package/src/server/rules/formula-action.test.ts +175 -0
- package/src/server/rules/handlebars-helpers.ts +131 -0
- package/src/server/rules/index.test.ts +1095 -0
- package/src/server/rules/index.ts +22 -0
- package/src/server/rules/rule-indexer.ts +89 -0
- package/src/server/rules/rule-utils.ts +274 -0
- package/src/server/rules/rule.ts +193 -0
- package/src/server/schedules/app.test.ts +502 -0
- package/src/server/schedules/app.ts +644 -0
- package/src/server/schedules/find-schedules.ts +391 -0
- package/src/server/server-config.ts +59 -0
- package/src/server/sheet.test.ts +101 -0
- package/src/server/sheet.ts +280 -0
- package/src/server/spreadsheet/__snapshots__/spreadsheet.test.ts.snap +5 -0
- package/src/server/spreadsheet/app.ts +54 -0
- package/src/server/spreadsheet/globals.ts +13 -0
- package/src/server/spreadsheet/graph-data-structure.ts +165 -0
- package/src/server/spreadsheet/scratch +60 -0
- package/src/server/spreadsheet/spreadsheet.test.ts +191 -0
- package/src/server/spreadsheet/spreadsheet.ts +523 -0
- package/src/server/spreadsheet/util.ts +15 -0
- package/src/server/sql/init.sql +88 -0
- package/src/server/sync/__snapshots__/sync.test.ts.snap +31 -0
- package/src/server/sync/app.ts +29 -0
- package/src/server/sync/encoder.ts +129 -0
- package/src/server/sync/index.ts +820 -0
- package/src/server/sync/make-test-message.ts +19 -0
- package/src/server/sync/migrate.test.ts +169 -0
- package/src/server/sync/migrate.ts +48 -0
- package/src/server/sync/repair.ts +39 -0
- package/src/server/sync/reset.ts +91 -0
- package/src/server/sync/sync.property.test.ts +385 -0
- package/src/server/sync/sync.test.ts +349 -0
- package/src/server/sync/utils.ts +3 -0
- package/src/server/tags/app.ts +101 -0
- package/src/server/tests/mockData.json +9352 -0
- package/src/server/tests/mockSyncServer.ts +119 -0
- package/src/server/tools/app.ts +152 -0
- package/src/server/transactions/__snapshots__/transaction-rules.test.ts.snap +173 -0
- package/src/server/transactions/__snapshots__/transfer.test.ts.snap +655 -0
- package/src/server/transactions/app.ts +136 -0
- package/src/server/transactions/export/export-to-csv.ts +132 -0
- package/src/server/transactions/import/__snapshots__/parse-file.test.ts.snap +1582 -0
- package/src/server/transactions/import/ofx2json.test.ts +33 -0
- package/src/server/transactions/import/ofx2json.ts +157 -0
- package/src/server/transactions/import/parse-file.test.ts +224 -0
- package/src/server/transactions/import/parse-file.ts +286 -0
- package/src/server/transactions/import/qif2json.ts +110 -0
- package/src/server/transactions/import/xmlcamt2json.ts +168 -0
- package/src/server/transactions/index.ts +196 -0
- package/src/server/transactions/merge.test.ts +370 -0
- package/src/server/transactions/merge.ts +139 -0
- package/src/server/transactions/transaction-rules.test.ts +994 -0
- package/src/server/transactions/transaction-rules.ts +1038 -0
- package/src/server/transactions/transfer.test.ts +221 -0
- package/src/server/transactions/transfer.ts +173 -0
- package/src/server/undo.ts +271 -0
- package/src/server/update.ts +37 -0
- package/src/server/util/budget-name.ts +61 -0
- package/src/server/util/custom-sync-mapping.ts +48 -0
- package/src/server/util/rschedule.ts +9 -0
- package/src/shared/__mocks__/platform.ts +7 -0
- package/src/shared/__snapshots__/months.test.ts.snap +21 -0
- package/src/shared/arithmetic.test.ts +112 -0
- package/src/shared/arithmetic.ts +170 -0
- package/src/shared/async.test.ts +135 -0
- package/src/shared/async.ts +76 -0
- package/src/shared/constants.ts +5 -0
- package/src/shared/currencies.ts +70 -0
- package/src/shared/dashboard.ts +260 -0
- package/src/shared/environment.ts +18 -0
- package/src/shared/errors.ts +195 -0
- package/src/shared/locale.ts +27 -0
- package/src/shared/location-utils.test.ts +69 -0
- package/src/shared/location-utils.ts +49 -0
- package/src/shared/months.test.ts +5 -0
- package/src/shared/months.ts +485 -0
- package/src/shared/normalisation.ts +6 -0
- package/src/shared/platform.electron.ts +21 -0
- package/src/shared/platform.ts +20 -0
- package/src/shared/query.ts +176 -0
- package/src/shared/rules.test.ts +56 -0
- package/src/shared/rules.ts +371 -0
- package/src/shared/schedules.test.ts +570 -0
- package/src/shared/schedules.ts +560 -0
- package/src/shared/test-helpers.ts +156 -0
- package/src/shared/transactions.test.ts +275 -0
- package/src/shared/transactions.ts +433 -0
- package/src/shared/transfer.test.ts +75 -0
- package/src/shared/transfer.ts +16 -0
- package/src/shared/user.ts +4 -0
- package/src/shared/util.test.ts +240 -0
- package/src/shared/util.ts +633 -0
- package/src/types/api-handlers.ts +287 -0
- package/src/types/budget.ts +8 -0
- package/src/types/file.ts +47 -0
- package/src/types/handlers.ts +46 -0
- package/src/types/models/account.ts +24 -0
- package/src/types/models/bank-sync.ts +23 -0
- package/src/types/models/bank.ts +6 -0
- package/src/types/models/category-group.ts +11 -0
- package/src/types/models/category.ts +13 -0
- package/src/types/models/dashboard.ts +199 -0
- package/src/types/models/gocardless.ts +84 -0
- package/src/types/models/import-transaction.ts +56 -0
- package/src/types/models/index.ts +23 -0
- package/src/types/models/nearby-payee.ts +7 -0
- package/src/types/models/note.ts +4 -0
- package/src/types/models/openid.ts +8 -0
- package/src/types/models/payee-location.ts +8 -0
- package/src/types/models/payee.ts +10 -0
- package/src/types/models/pluggyai.ts +19 -0
- package/src/types/models/reports.ts +144 -0
- package/src/types/models/rule.ts +174 -0
- package/src/types/models/schedule.ts +49 -0
- package/src/types/models/simplefin.ts +28 -0
- package/src/types/models/tags.ts +6 -0
- package/src/types/models/templates.ts +135 -0
- package/src/types/models/transaction-filter.ts +9 -0
- package/src/types/models/transaction.ts +39 -0
- package/src/types/models/user-access.ts +10 -0
- package/src/types/models/user.ts +25 -0
- package/src/types/prefs.ts +167 -0
- package/src/types/server-events.ts +86 -0
- package/src/types/server-handlers.ts +27 -0
- package/src/types/util.ts +26 -0
- package/tsconfig.json +34 -0
- package/typings/pegjs.ts +1 -0
- package/typings/process-worker.ts +12 -0
- package/typings/vite-plugin-peggy-loader.ts +1 -0
- package/typings/window.ts +62 -0
- package/vite.config.ts +109 -0
- package/vite.desktop.config.ts +59 -0
- package/vitest.config.ts +43 -0
- package/vitest.web.config.ts +38 -0
|
@@ -0,0 +1,679 @@
|
|
|
1
|
+
// @ts-strict-ignore
|
|
2
|
+
import * as asyncStorage from '../../platform/server/asyncStorage';
|
|
3
|
+
import * as monthUtils from '../../shared/months';
|
|
4
|
+
import type { SyncedPrefs } from '../../types/prefs';
|
|
5
|
+
import * as db from '../db';
|
|
6
|
+
import { loadMappings } from '../db/mappings';
|
|
7
|
+
import { post } from '../post';
|
|
8
|
+
import { getServer } from '../server-config';
|
|
9
|
+
import { handlers } from '../tests/mockSyncServer';
|
|
10
|
+
import { insertRule, loadRules } from '../transactions/transaction-rules';
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
addTransactions,
|
|
14
|
+
reconcileTransactions,
|
|
15
|
+
simpleFinBatchSync,
|
|
16
|
+
} from './sync';
|
|
17
|
+
|
|
18
|
+
vi.mock('../../shared/months', async () => ({
|
|
19
|
+
...(await vi.importActual('../../shared/months')),
|
|
20
|
+
currentDay: vi.fn(),
|
|
21
|
+
currentMonth: vi.fn(),
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
beforeEach(async () => {
|
|
25
|
+
vi.resetAllMocks();
|
|
26
|
+
vi.mocked(monthUtils.currentDay).mockReturnValue('2017-10-15');
|
|
27
|
+
vi.mocked(monthUtils.currentMonth).mockReturnValue('2017-10');
|
|
28
|
+
await global.emptyDatabase()();
|
|
29
|
+
await loadMappings();
|
|
30
|
+
await loadRules();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
function getAllTransactions() {
|
|
34
|
+
return db.all<
|
|
35
|
+
db.DbViewTransactionInternal & { payee_name: db.DbPayee['name'] }
|
|
36
|
+
>(
|
|
37
|
+
`SELECT t.*, p.name as payee_name
|
|
38
|
+
FROM v_transactions_internal t
|
|
39
|
+
LEFT JOIN payees p ON p.id = t.payee
|
|
40
|
+
ORDER BY date DESC, amount DESC, id
|
|
41
|
+
`,
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function prepareDatabase() {
|
|
46
|
+
await db.insertCategoryGroup({ id: 'group1', name: 'group1', is_income: 1 });
|
|
47
|
+
await db.insertCategory({
|
|
48
|
+
name: 'income',
|
|
49
|
+
cat_group: 'group1',
|
|
50
|
+
is_income: 1,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const { accounts } = await post(getServer().GOCARDLESS_SERVER + '/accounts', {
|
|
54
|
+
client_id: '',
|
|
55
|
+
group_id: '',
|
|
56
|
+
item_id: '1',
|
|
57
|
+
});
|
|
58
|
+
const acct = accounts[0];
|
|
59
|
+
|
|
60
|
+
const id = await db.insertAccount({
|
|
61
|
+
id: 'one',
|
|
62
|
+
account_id: acct.account_id,
|
|
63
|
+
name: acct.official_name,
|
|
64
|
+
balance_current: acct.balances.current,
|
|
65
|
+
});
|
|
66
|
+
await db.insertPayee({
|
|
67
|
+
id: 'transfer-' + id,
|
|
68
|
+
name: '',
|
|
69
|
+
transfer_acct: id,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
return { id, account_id: acct.account_id };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function getAllPayees() {
|
|
76
|
+
return (await db.getPayees()).filter(p => p.transfer_acct == null);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
describe('Account sync', () => {
|
|
80
|
+
test('reconcile creates payees correctly', async () => {
|
|
81
|
+
const { id } = await prepareDatabase();
|
|
82
|
+
|
|
83
|
+
let payees = await getAllPayees();
|
|
84
|
+
expect(payees.length).toBe(0);
|
|
85
|
+
|
|
86
|
+
await reconcileTransactions(id, [
|
|
87
|
+
{ date: '2020-01-02', payee_name: 'bakkerij', amount: 4133 },
|
|
88
|
+
{ date: '2020-01-03', payee_name: 'kroger', amount: 5000 },
|
|
89
|
+
]);
|
|
90
|
+
|
|
91
|
+
payees = await getAllPayees();
|
|
92
|
+
expect(payees.length).toBe(2);
|
|
93
|
+
|
|
94
|
+
const transactions = await getAllTransactions();
|
|
95
|
+
expect(transactions.length).toBe(2);
|
|
96
|
+
expect(transactions.find(t => t.amount === 4133).payee).toBe(
|
|
97
|
+
payees.find(p => p.name === 'Bakkerij').id,
|
|
98
|
+
);
|
|
99
|
+
expect(transactions.find(t => t.amount === 5000).payee).toBe(
|
|
100
|
+
payees.find(p => p.name === 'Kroger').id,
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('reconcile handles transactions with undefined fields', async () => {
|
|
105
|
+
const { id: acctId } = await prepareDatabase();
|
|
106
|
+
|
|
107
|
+
await db.insertTransaction({
|
|
108
|
+
id: 'one',
|
|
109
|
+
account: acctId,
|
|
110
|
+
amount: 2948,
|
|
111
|
+
date: '2020-01-01',
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
await reconcileTransactions(acctId, [
|
|
115
|
+
{ date: '2020-01-02' },
|
|
116
|
+
{ date: '2020-01-01', amount: 2948 },
|
|
117
|
+
]);
|
|
118
|
+
|
|
119
|
+
const transactions = await getAllTransactions();
|
|
120
|
+
expect(transactions.length).toBe(2);
|
|
121
|
+
expect(transactions).toMatchSnapshot();
|
|
122
|
+
|
|
123
|
+
// No payees should be created
|
|
124
|
+
const payees = await getAllPayees();
|
|
125
|
+
expect(payees.length).toBe(0);
|
|
126
|
+
|
|
127
|
+
// Make _at least_ the date is required
|
|
128
|
+
await expect(reconcileTransactions(acctId, [{}])).rejects.toThrow(
|
|
129
|
+
/`date` is required/,
|
|
130
|
+
);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('reconcile doesnt rematch deleted transactions if reimport disabled', async () => {
|
|
134
|
+
const { id: acctId } = await prepareDatabase();
|
|
135
|
+
const reimportKey =
|
|
136
|
+
`sync-reimport-deleted-${acctId}` satisfies keyof SyncedPrefs;
|
|
137
|
+
await db.update('preferences', { id: reimportKey, value: 'false' });
|
|
138
|
+
|
|
139
|
+
await reconcileTransactions(acctId, [
|
|
140
|
+
{ date: '2020-01-01', imported_id: 'finid' },
|
|
141
|
+
]);
|
|
142
|
+
|
|
143
|
+
const transactions1 = await getAllTransactions();
|
|
144
|
+
expect(transactions1.length).toBe(1);
|
|
145
|
+
|
|
146
|
+
await db.deleteTransaction(transactions1[0]);
|
|
147
|
+
|
|
148
|
+
await reconcileTransactions(acctId, [
|
|
149
|
+
{ date: '2020-01-01', imported_id: 'finid' },
|
|
150
|
+
]);
|
|
151
|
+
const transactions2 = await getAllTransactions();
|
|
152
|
+
expect(transactions2.length).toBe(1);
|
|
153
|
+
expect(transactions2).toMatchSnapshot();
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test('reconcile does rematch deleted transactions by default', async () => {
|
|
157
|
+
const { id: acctId } = await prepareDatabase();
|
|
158
|
+
|
|
159
|
+
await reconcileTransactions(acctId, [
|
|
160
|
+
{ date: '2020-01-01', imported_id: 'finid' },
|
|
161
|
+
]);
|
|
162
|
+
|
|
163
|
+
const transactions1 = await getAllTransactions();
|
|
164
|
+
expect(transactions1.length).toBe(1);
|
|
165
|
+
|
|
166
|
+
await db.deleteTransaction(transactions1[0]);
|
|
167
|
+
|
|
168
|
+
await reconcileTransactions(acctId, [
|
|
169
|
+
{ date: '2020-01-01', imported_id: 'finid' },
|
|
170
|
+
]);
|
|
171
|
+
const transactions2 = await getAllTransactions();
|
|
172
|
+
expect(transactions2.length).toBe(2);
|
|
173
|
+
expect(transactions2).toMatchSnapshot();
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test('reconcile run rules with inferred payee', async () => {
|
|
177
|
+
const { id: acctId } = await prepareDatabase();
|
|
178
|
+
await db.insertCategoryGroup({
|
|
179
|
+
id: 'group2',
|
|
180
|
+
name: 'group2',
|
|
181
|
+
});
|
|
182
|
+
const catId = await db.insertCategory({
|
|
183
|
+
name: 'Food',
|
|
184
|
+
cat_group: 'group2',
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const payeeId = await db.insertPayee({ name: 'bakkerij' });
|
|
188
|
+
|
|
189
|
+
await insertRule({
|
|
190
|
+
stage: null,
|
|
191
|
+
conditionsOp: 'and',
|
|
192
|
+
conditions: [{ op: 'is', field: 'payee', value: payeeId }],
|
|
193
|
+
actions: [{ op: 'set', field: 'category', value: catId }],
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
await reconcileTransactions(acctId, [
|
|
197
|
+
{ date: '2020-01-02', payee_name: 'Bakkerij', amount: 4133 },
|
|
198
|
+
]);
|
|
199
|
+
|
|
200
|
+
const transactions = await getAllTransactions();
|
|
201
|
+
// Even though the payee was inferred from the string name (no
|
|
202
|
+
// renaming rules ran), it should match the above rule and set the
|
|
203
|
+
// category
|
|
204
|
+
expect(transactions.length).toBe(1);
|
|
205
|
+
expect(transactions[0].payee).toBe(payeeId);
|
|
206
|
+
expect(transactions[0].category).toBe(catId);
|
|
207
|
+
|
|
208
|
+
// It also should not have created a payee
|
|
209
|
+
const payees = await getAllPayees();
|
|
210
|
+
expect(payees.length).toBe(1);
|
|
211
|
+
expect(payees[0].id).toBe(payeeId);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test('reconcile avoids creating blank payees', async () => {
|
|
215
|
+
const { id: acctId } = await prepareDatabase();
|
|
216
|
+
|
|
217
|
+
await reconcileTransactions(acctId, [
|
|
218
|
+
{ date: '2020-01-02', payee_name: ' ', amount: 4133 },
|
|
219
|
+
]);
|
|
220
|
+
|
|
221
|
+
const transactions = await getAllTransactions();
|
|
222
|
+
// Even though the payee was inferred from the string name (no
|
|
223
|
+
// renaming rules ran), it should match the above rule and set the
|
|
224
|
+
// category
|
|
225
|
+
expect(transactions.length).toBe(1);
|
|
226
|
+
expect(transactions[0].payee).toBe(null);
|
|
227
|
+
expect(transactions[0].amount).toBe(4133);
|
|
228
|
+
expect(transactions[0].date).toBe(20200102);
|
|
229
|
+
|
|
230
|
+
// It also should not have created a payee
|
|
231
|
+
const payees = await getAllPayees();
|
|
232
|
+
expect(payees.length).toBe(0);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test('reconcile run rules dont create unnecessary payees', async () => {
|
|
236
|
+
const { id: acctId } = await prepareDatabase();
|
|
237
|
+
|
|
238
|
+
const payeeId = await db.insertPayee({ name: 'bakkerij-renamed' });
|
|
239
|
+
|
|
240
|
+
await insertRule({
|
|
241
|
+
stage: null,
|
|
242
|
+
conditionsOp: 'and',
|
|
243
|
+
conditions: [{ op: 'is', field: 'imported_payee', value: 'Bakkerij' }],
|
|
244
|
+
actions: [{ op: 'set', field: 'payee', value: payeeId }],
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
await reconcileTransactions(acctId, [
|
|
248
|
+
{ date: '2020-01-02', payee_name: 'bakkerij', amount: 4133 },
|
|
249
|
+
]);
|
|
250
|
+
|
|
251
|
+
const payees = await getAllPayees();
|
|
252
|
+
expect(payees.length).toBe(1);
|
|
253
|
+
expect(payees[0].id).toBe(payeeId);
|
|
254
|
+
|
|
255
|
+
const transactions = await getAllTransactions();
|
|
256
|
+
expect(transactions.length).toBe(1);
|
|
257
|
+
expect(transactions[0].payee).toBe(payeeId);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
const testMapped = version => {
|
|
261
|
+
test(`reconcile matches unmapped and mapped payees (${version})`, async () => {
|
|
262
|
+
const { id: acctId } = await prepareDatabase();
|
|
263
|
+
|
|
264
|
+
if (version === 'v1') {
|
|
265
|
+
// This is quite complicated, but important to test. If a payee is
|
|
266
|
+
// merged with another, a rule sets the payee of a transaction to
|
|
267
|
+
// the updated one, make sure it still matches an existing
|
|
268
|
+
// transaction that points to the old merged payee
|
|
269
|
+
} else if (version === 'v2') {
|
|
270
|
+
// This is similar to v1, but inverted: make sure that
|
|
271
|
+
// if a rule sets the payee to an *old* payee, that it still
|
|
272
|
+
// matches to a transaction with the new payee that it was merged
|
|
273
|
+
// to
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const payeeId1 = await db.insertPayee({ name: 'bakkerij2' });
|
|
277
|
+
const payeeId2 = await db.insertPayee({ name: 'bakkerij-renamed' });
|
|
278
|
+
|
|
279
|
+
// Insert a rule *before* payees are merged. Not that v2 would
|
|
280
|
+
// fail if we inserted this rule after, because the rule would
|
|
281
|
+
// set to an *old* payee but the matching would take place on a
|
|
282
|
+
// *new* payee. But that's ok - it would fallback to matching
|
|
283
|
+
// amount anyway, so while it loses some fidelity, it's an edge
|
|
284
|
+
// case that we don't need to worry much about because the user
|
|
285
|
+
// shouldn't be able able to create rules for a merged payee.
|
|
286
|
+
// Unless they sync in a rule...
|
|
287
|
+
await insertRule({
|
|
288
|
+
stage: null,
|
|
289
|
+
conditionsOp: 'and',
|
|
290
|
+
conditions: [{ op: 'is', field: 'imported_payee', value: 'Bakkerij' }],
|
|
291
|
+
actions: [{ op: 'set', field: 'payee', value: payeeId2 }],
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
if (version === 'v1') {
|
|
295
|
+
await db.mergePayees(payeeId2, [payeeId1]);
|
|
296
|
+
} else if (version === 'v2') {
|
|
297
|
+
await db.mergePayees(payeeId1, [payeeId2]);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
await db.insertTransaction({
|
|
301
|
+
id: 'one',
|
|
302
|
+
account: acctId,
|
|
303
|
+
amount: -2947,
|
|
304
|
+
date: '2017-10-15',
|
|
305
|
+
payee: payeeId1,
|
|
306
|
+
});
|
|
307
|
+
// It will try to match to this one first, make sure it matches
|
|
308
|
+
// the above transaction though
|
|
309
|
+
await db.insertTransaction({
|
|
310
|
+
id: 'two',
|
|
311
|
+
account: acctId,
|
|
312
|
+
amount: -2947,
|
|
313
|
+
date: '2017-10-17',
|
|
314
|
+
payee: null,
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
const { updated } = await reconcileTransactions(acctId, [
|
|
318
|
+
{
|
|
319
|
+
date: '2017-10-17',
|
|
320
|
+
payee_name: 'bakkerij',
|
|
321
|
+
amount: -2947,
|
|
322
|
+
imported_id: 'imported1',
|
|
323
|
+
},
|
|
324
|
+
]);
|
|
325
|
+
|
|
326
|
+
const payees = await getAllPayees();
|
|
327
|
+
expect(payees.length).toBe(1);
|
|
328
|
+
expect(payees[0].id).toBe(version === 'v1' ? payeeId2 : payeeId1);
|
|
329
|
+
|
|
330
|
+
expect(updated.length).toBe(1);
|
|
331
|
+
expect(updated[0]).toBe('one');
|
|
332
|
+
|
|
333
|
+
const transactions = await getAllTransactions();
|
|
334
|
+
expect(transactions.length).toBe(2);
|
|
335
|
+
expect(transactions.find(t => t.id === 'one').imported_id).toBe(
|
|
336
|
+
'imported1',
|
|
337
|
+
);
|
|
338
|
+
});
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
testMapped('v1');
|
|
342
|
+
testMapped('v2');
|
|
343
|
+
|
|
344
|
+
test('addTransactions simply adds transactions', async () => {
|
|
345
|
+
const { id: acctId } = await prepareDatabase();
|
|
346
|
+
|
|
347
|
+
const payeeId = await db.insertPayee({ name: 'bakkerij-renamed' });
|
|
348
|
+
|
|
349
|
+
// Make sure it still runs rules
|
|
350
|
+
await insertRule({
|
|
351
|
+
stage: null,
|
|
352
|
+
conditionsOp: 'and',
|
|
353
|
+
conditions: [{ op: 'is', field: 'imported_payee', value: 'Bakkerij' }],
|
|
354
|
+
actions: [{ op: 'set', field: 'payee', value: payeeId }],
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
const transactions = [
|
|
358
|
+
{
|
|
359
|
+
date: '2017-10-17',
|
|
360
|
+
payee_name: 'BAKKerij',
|
|
361
|
+
amount: -2947,
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
date: '2017-10-18',
|
|
365
|
+
payee_name: 'bakkERIj2',
|
|
366
|
+
amount: -2947,
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
date: '2017-10-19',
|
|
370
|
+
payee_name: 'bakkerij3',
|
|
371
|
+
amount: -2947,
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
date: '2017-10-20',
|
|
375
|
+
payee_name: 'BakkeriJ3',
|
|
376
|
+
amount: -2947,
|
|
377
|
+
},
|
|
378
|
+
];
|
|
379
|
+
|
|
380
|
+
const added = await addTransactions(acctId, transactions);
|
|
381
|
+
expect(added.length).toBe(transactions.length);
|
|
382
|
+
|
|
383
|
+
const payees = await getAllPayees();
|
|
384
|
+
expect(payees.length).toBe(3);
|
|
385
|
+
|
|
386
|
+
const getName = id => payees.find(p => p.id === id).name;
|
|
387
|
+
|
|
388
|
+
const allTransactions = await getAllTransactions();
|
|
389
|
+
expect(allTransactions.length).toBe(4);
|
|
390
|
+
expect(allTransactions.map(t => getName(t.payee))).toEqual([
|
|
391
|
+
'bakkerij3',
|
|
392
|
+
'bakkerij3',
|
|
393
|
+
'bakkERIj2',
|
|
394
|
+
'bakkerij-renamed',
|
|
395
|
+
]);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
test("reconcile does not merge transactions with different 'imported_id' values", async () => {
|
|
399
|
+
const { id } = await prepareDatabase();
|
|
400
|
+
|
|
401
|
+
let payees = await getAllPayees();
|
|
402
|
+
expect(payees.length).toBe(0);
|
|
403
|
+
|
|
404
|
+
// Add first transaction
|
|
405
|
+
await reconcileTransactions(id, [
|
|
406
|
+
{
|
|
407
|
+
date: '2024-04-05',
|
|
408
|
+
amount: -1239,
|
|
409
|
+
imported_payee: 'Acme Inc.',
|
|
410
|
+
payee_name: 'Acme Inc.',
|
|
411
|
+
imported_id: 'b85cdd57-5a1c-4ca5-bd54-12e5b56fa02c',
|
|
412
|
+
notes: 'TEST TRANSACTION',
|
|
413
|
+
cleared: true,
|
|
414
|
+
},
|
|
415
|
+
]);
|
|
416
|
+
|
|
417
|
+
payees = await getAllPayees();
|
|
418
|
+
expect(payees.length).toBe(1);
|
|
419
|
+
|
|
420
|
+
let transactions = await getAllTransactions();
|
|
421
|
+
expect(transactions.length).toBe(1);
|
|
422
|
+
|
|
423
|
+
// Add second transaction
|
|
424
|
+
await reconcileTransactions(id, [
|
|
425
|
+
{
|
|
426
|
+
date: '2024-04-06',
|
|
427
|
+
amount: -1239,
|
|
428
|
+
imported_payee: 'Acme Inc.',
|
|
429
|
+
payee_name: 'Acme Inc.',
|
|
430
|
+
imported_id: 'ca1589b2-7bc3-4587-a157-476170b383a7',
|
|
431
|
+
notes: 'TEST TRANSACTION',
|
|
432
|
+
cleared: true,
|
|
433
|
+
},
|
|
434
|
+
]);
|
|
435
|
+
|
|
436
|
+
payees = await getAllPayees();
|
|
437
|
+
expect(payees.length).toBe(1);
|
|
438
|
+
|
|
439
|
+
transactions = await getAllTransactions();
|
|
440
|
+
expect(transactions.length).toBe(2);
|
|
441
|
+
|
|
442
|
+
expect(
|
|
443
|
+
transactions.find(
|
|
444
|
+
t => t.imported_id === 'b85cdd57-5a1c-4ca5-bd54-12e5b56fa02c',
|
|
445
|
+
).amount,
|
|
446
|
+
).toBe(-1239);
|
|
447
|
+
expect(
|
|
448
|
+
transactions.find(
|
|
449
|
+
t => t.imported_id === 'ca1589b2-7bc3-4587-a157-476170b383a7',
|
|
450
|
+
).amount,
|
|
451
|
+
).toBe(-1239);
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
test(
|
|
455
|
+
'given an imported tx with no imported_id, ' +
|
|
456
|
+
'when using fuzzy search V2, existing transaction has an imported_id, matches amount, and is within 7 days of imported tx, ' +
|
|
457
|
+
'then imported tx should reconcile with existing transaction from fuzzy match',
|
|
458
|
+
async () => {
|
|
459
|
+
const { id } = await prepareDatabase();
|
|
460
|
+
|
|
461
|
+
let payees = await getAllPayees();
|
|
462
|
+
expect(payees.length).toBe(0);
|
|
463
|
+
|
|
464
|
+
const existingTx = {
|
|
465
|
+
date: '2024-04-05',
|
|
466
|
+
amount: -1239,
|
|
467
|
+
imported_payee: 'Acme Inc.',
|
|
468
|
+
payee_name: 'Acme Inc.',
|
|
469
|
+
imported_id: 'b85cdd57-5a1c-4ca5-bd54-12e5b56fa02c',
|
|
470
|
+
notes: 'TEST TRANSACTION',
|
|
471
|
+
cleared: true,
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
// Add transaction to represent existing transaction with imoprted_id
|
|
475
|
+
await reconcileTransactions(id, [existingTx]);
|
|
476
|
+
|
|
477
|
+
payees = await getAllPayees();
|
|
478
|
+
expect(payees.length).toBe(1);
|
|
479
|
+
|
|
480
|
+
let transactions = await getAllTransactions();
|
|
481
|
+
expect(transactions.length).toBe(1);
|
|
482
|
+
|
|
483
|
+
// Import transaction similar to existing but with different date and no imported_id
|
|
484
|
+
await reconcileTransactions(id, [
|
|
485
|
+
{
|
|
486
|
+
...existingTx,
|
|
487
|
+
date: '2024-04-06',
|
|
488
|
+
imported_id: null,
|
|
489
|
+
},
|
|
490
|
+
]);
|
|
491
|
+
|
|
492
|
+
payees = await getAllPayees();
|
|
493
|
+
expect(payees.length).toBe(1);
|
|
494
|
+
|
|
495
|
+
transactions = await getAllTransactions();
|
|
496
|
+
expect(transactions.length).toBe(1);
|
|
497
|
+
|
|
498
|
+
expect(transactions[0].amount).toBe(-1239);
|
|
499
|
+
},
|
|
500
|
+
);
|
|
501
|
+
|
|
502
|
+
test(
|
|
503
|
+
'given an imported tx has an imported_id, ' +
|
|
504
|
+
'when not using fuzzy search V2, existing transaction has an imported_id, matches amount, and is within 7 days of imported tx, ' +
|
|
505
|
+
'then imported tx should reconcile with existing transaction from fuzzy match',
|
|
506
|
+
async () => {
|
|
507
|
+
const { id } = await prepareDatabase();
|
|
508
|
+
|
|
509
|
+
let payees = await getAllPayees();
|
|
510
|
+
expect(payees.length).toBe(0);
|
|
511
|
+
|
|
512
|
+
const existingTx = {
|
|
513
|
+
date: '2024-04-05',
|
|
514
|
+
amount: -1239,
|
|
515
|
+
imported_payee: 'Acme Inc.',
|
|
516
|
+
payee_name: 'Acme Inc.',
|
|
517
|
+
imported_id: 'b85cdd57-5a1c-4ca5-bd54-12e5b56fa02c',
|
|
518
|
+
notes: 'TEST TRANSACTION',
|
|
519
|
+
cleared: true,
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
// Add transaction to represent existing transaction with imoprted_id
|
|
523
|
+
await reconcileTransactions(id, [existingTx]);
|
|
524
|
+
|
|
525
|
+
payees = await getAllPayees();
|
|
526
|
+
expect(payees.length).toBe(1);
|
|
527
|
+
|
|
528
|
+
let transactions = await getAllTransactions();
|
|
529
|
+
expect(transactions.length).toBe(1);
|
|
530
|
+
|
|
531
|
+
// Import transaction similar to existing but with different date and imported_id
|
|
532
|
+
await reconcileTransactions(
|
|
533
|
+
id,
|
|
534
|
+
[
|
|
535
|
+
{
|
|
536
|
+
...existingTx,
|
|
537
|
+
date: '2024-04-06',
|
|
538
|
+
imported_id: 'something-else-entirely',
|
|
539
|
+
},
|
|
540
|
+
],
|
|
541
|
+
false,
|
|
542
|
+
false,
|
|
543
|
+
);
|
|
544
|
+
|
|
545
|
+
payees = await getAllPayees();
|
|
546
|
+
expect(payees.length).toBe(1);
|
|
547
|
+
|
|
548
|
+
transactions = await getAllTransactions();
|
|
549
|
+
expect(transactions.length).toBe(1);
|
|
550
|
+
|
|
551
|
+
expect(transactions[0].amount).toBe(-1239);
|
|
552
|
+
},
|
|
553
|
+
);
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
describe('SimpleFin batch sync', () => {
|
|
557
|
+
function mockSimpleFinTransactions(response) {
|
|
558
|
+
vi.mocked(asyncStorage.getItem).mockResolvedValue('test-token');
|
|
559
|
+
handlers['/simplefin/transactions'] = () => response;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
afterEach(() => {
|
|
563
|
+
delete handlers['/simplefin/transactions'];
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
test('returns ACCOUNT_MISSING error when an account is not in the response', async () => {
|
|
567
|
+
const presentAccountId = 'sf-account-1';
|
|
568
|
+
const missingAccountId = 'sf-account-2';
|
|
569
|
+
|
|
570
|
+
// Mock SimpleFin response that only returns data for one of two accounts
|
|
571
|
+
mockSimpleFinTransactions({
|
|
572
|
+
[presentAccountId]: {
|
|
573
|
+
transactions: { all: [], booked: [], pending: [] },
|
|
574
|
+
balances: [],
|
|
575
|
+
startingBalance: 0,
|
|
576
|
+
},
|
|
577
|
+
errors: {},
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
// Insert two accounts linked to SimpleFin
|
|
581
|
+
const acct1Id = await db.insertAccount({
|
|
582
|
+
id: 'acct-1',
|
|
583
|
+
account_id: presentAccountId,
|
|
584
|
+
name: 'Account 1',
|
|
585
|
+
account_sync_source: 'simpleFin',
|
|
586
|
+
});
|
|
587
|
+
await db.insertPayee({
|
|
588
|
+
id: 'transfer-' + acct1Id,
|
|
589
|
+
name: '',
|
|
590
|
+
transfer_acct: acct1Id,
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
const acct2Id = await db.insertAccount({
|
|
594
|
+
id: 'acct-2',
|
|
595
|
+
account_id: missingAccountId,
|
|
596
|
+
name: 'Account 2',
|
|
597
|
+
account_sync_source: 'simpleFin',
|
|
598
|
+
});
|
|
599
|
+
await db.insertPayee({
|
|
600
|
+
id: 'transfer-' + acct2Id,
|
|
601
|
+
name: '',
|
|
602
|
+
transfer_acct: acct2Id,
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
const results = await simpleFinBatchSync([
|
|
606
|
+
{ id: 'acct-1', account_id: presentAccountId },
|
|
607
|
+
{ id: 'acct-2', account_id: missingAccountId },
|
|
608
|
+
]);
|
|
609
|
+
|
|
610
|
+
// The present account should succeed (no error_code)
|
|
611
|
+
const presentResult = results.find(r => r.accountId === 'acct-1');
|
|
612
|
+
expect(presentResult).toBeDefined();
|
|
613
|
+
expect(presentResult.res.error_code).toBeUndefined();
|
|
614
|
+
|
|
615
|
+
// The missing account should have ACCOUNT_MISSING error
|
|
616
|
+
const missingResult = results.find(r => r.accountId === 'acct-2');
|
|
617
|
+
expect(missingResult).toBeDefined();
|
|
618
|
+
expect(missingResult.res.error_code).toBe('ACCOUNT_MISSING');
|
|
619
|
+
expect(missingResult.res.error_type).toBe('ACCOUNT_MISSING');
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
test('propagates ACCOUNT_MISSING error from SimpleFin response errors', async () => {
|
|
623
|
+
const presentAccountId = 'sf-account-1';
|
|
624
|
+
const missingAccountId = 'sf-account-2';
|
|
625
|
+
|
|
626
|
+
// Mock SimpleFin response with error entry for missing account
|
|
627
|
+
mockSimpleFinTransactions({
|
|
628
|
+
[presentAccountId]: {
|
|
629
|
+
transactions: { all: [], booked: [], pending: [] },
|
|
630
|
+
balances: [],
|
|
631
|
+
startingBalance: 0,
|
|
632
|
+
},
|
|
633
|
+
errors: {
|
|
634
|
+
[missingAccountId]: [
|
|
635
|
+
{
|
|
636
|
+
error_type: 'ACCOUNT_MISSING',
|
|
637
|
+
error_code: 'ACCOUNT_MISSING',
|
|
638
|
+
reason: 'Account not found',
|
|
639
|
+
},
|
|
640
|
+
],
|
|
641
|
+
},
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
const acct1Id = await db.insertAccount({
|
|
645
|
+
id: 'acct-1',
|
|
646
|
+
account_id: presentAccountId,
|
|
647
|
+
name: 'Account 1',
|
|
648
|
+
account_sync_source: 'simpleFin',
|
|
649
|
+
});
|
|
650
|
+
await db.insertPayee({
|
|
651
|
+
id: 'transfer-' + acct1Id,
|
|
652
|
+
name: '',
|
|
653
|
+
transfer_acct: acct1Id,
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
const acct2Id = await db.insertAccount({
|
|
657
|
+
id: 'acct-2',
|
|
658
|
+
account_id: missingAccountId,
|
|
659
|
+
name: 'Account 2',
|
|
660
|
+
account_sync_source: 'simpleFin',
|
|
661
|
+
});
|
|
662
|
+
await db.insertPayee({
|
|
663
|
+
id: 'transfer-' + acct2Id,
|
|
664
|
+
name: '',
|
|
665
|
+
transfer_acct: acct2Id,
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
const results = await simpleFinBatchSync([
|
|
669
|
+
{ id: 'acct-1', account_id: presentAccountId },
|
|
670
|
+
{ id: 'acct-2', account_id: missingAccountId },
|
|
671
|
+
]);
|
|
672
|
+
|
|
673
|
+
// The missing account should get the ACCOUNT_MISSING error from the errors map
|
|
674
|
+
const missingResult = results.find(r => r.accountId === 'acct-2');
|
|
675
|
+
expect(missingResult).toBeDefined();
|
|
676
|
+
expect(missingResult.res.error_code).toBe('ACCOUNT_MISSING');
|
|
677
|
+
expect(missingResult.res.error_type).toBe('ACCOUNT_MISSING');
|
|
678
|
+
});
|
|
679
|
+
});
|