From 480c0356f9742f4a753012eee786c21aa610517f Mon Sep 17 00:00:00 2001 From: Martin Berg Alstad Date: Mon, 23 Dec 2024 16:47:07 +0100 Subject: [PATCH] Fixes - Added Actual account ids and log level to .env.example. - Fixed timestamp error by downloading the budget after init. - Hardcoded date in test. - Separate logging file for configurations. - Organized test - More logging in main - Fixed wrong paths for some files - Load a .env.test.local file when running tests Signed-off-by: Martin Berg Alstad --- .env.example | 6 +- package.json | 3 +- pnpm-lock.yaml | 119 ++++++++++++++++++++++++++++++++++------ src/actual.ts | 5 ++ src/logger.ts | 8 +++ src/main.ts | 20 ++++--- src/mappings.ts | 2 +- tests/main.test.ts | 10 +++- tests/stubs/bankStub.ts | 4 +- 9 files changed, 144 insertions(+), 33 deletions(-) create mode 100644 src/logger.ts diff --git a/.env.example b/.env.example index 1dc2bde..5be48ba 100644 --- a/.env.example +++ b/.env.example @@ -2,9 +2,13 @@ ACTUAL_BUDGET_ID=your-budget-id ACTUAL_SYNC_ID=your-sync-id ACTUAL_SERVER_URL=your-server-url ACTUAL_PASSWORD=your-password +ACTUAL_ACCOUNT_IDS=your-account-id1,your-account-id2 # Bank BANK_OAUTH_CLIENT_ID=your-client-id BANK_OAUTH_CLIENT_SECRET=your-client-secret BANK_OAUTH_STATE=your-state BANK_OAUTH_REDIRECT_URI=your-redirect-uri -BANK_ACCOUNT_IDS=your-account-id1,your-account-id2 \ No newline at end of file +BANK_ACCOUNT_IDS=your-account-id1,your-account-id2 +# Configuration +# trace | error | warn | info | debug | trace +LOG_LEVEL=info diff --git a/package.json b/package.json index c08c8c0..263a63f 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "index.js", "scripts": { "start": "node --import=tsx ./src/main.ts | pino-pretty", - "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js", + "test": "dotenvx run --env-file=.env.test.local -- node --experimental-vm-modules node_modules/jest/bin/jest.js | pino-pretty", "format": "prettier --write \"./**/*.{js,mjs,ts,md,json}\"" }, "keywords": [], @@ -13,6 +13,7 @@ "license": "ISC", "dependencies": { "@actual-app/api": "^24.12.0", + "@dotenvx/dotenvx": "^1.31.3", "cron": "^3.3.1", "dotenv": "^16.4.7", "pino": "^9.5.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 84d3078..e1cceef 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ dependencies: '@actual-app/api': specifier: ^24.12.0 version: 24.12.0 + '@dotenvx/dotenvx': + specifier: ^1.31.3 + version: 1.31.3 cron: specifier: ^3.3.1 version: 3.3.1 @@ -397,6 +400,30 @@ packages: '@jridgewell/trace-mapping': 0.3.9 dev: true + /@dotenvx/dotenvx@1.31.3: + resolution: {integrity: sha512-NgRjBV8NrCIoRhdbPozkKp+HvSn0Sc8DrOT22YDvTbs5pgPC2YrXKqwI7YwLFDVHBjSJHJTvkhQ5QHCCO+//yg==} + hasBin: true + dependencies: + commander: 11.1.0 + dotenv: 16.4.7 + eciesjs: 0.4.13 + execa: 5.1.1 + fdir: 6.4.2(picomatch@4.0.2) + ignore: 5.3.2 + object-treeify: 1.1.33 + picomatch: 4.0.2 + which: 4.0.0 + dev: false + + /@ecies/ciphers@0.2.2(@noble/ciphers@1.1.3): + resolution: {integrity: sha512-ylfGR7PyTd+Rm2PqQowG08BCKA22QuX8NzrL+LxAAvazN10DMwdJ2fWwAzRj05FI/M8vNFGm3cv9Wq/GFWCBLg==} + engines: {bun: '>=1', deno: '>=2', node: '>=16'} + peerDependencies: + '@noble/ciphers': ^1.0.0 + dependencies: + '@noble/ciphers': 1.1.3 + dev: false + /@esbuild/aix-ppc64@0.23.1: resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==} engines: {node: '>=18'} @@ -880,6 +907,28 @@ packages: '@jridgewell/sourcemap-codec': 1.5.0 dev: true + /@noble/ciphers@1.1.3: + resolution: {integrity: sha512-Ygv6WnWJHLLiW4fnNDC1z+i13bud+enXOFRBlpxI+NJliPWx5wdR+oWlTjLuBPTqjUjtHXtjkU6w3kuuH6upZA==} + engines: {node: ^14.21.3 || >=16} + dev: false + + /@noble/curves@1.7.0: + resolution: {integrity: sha512-UTMhXK9SeDhFJVrHeUJ5uZlI6ajXg10O6Ddocf9S6GjbSBVZsJo88HzKwXznNfGpMTRDyJkqMjNDPYgf0qFWnw==} + engines: {node: ^14.21.3 || >=16} + dependencies: + '@noble/hashes': 1.6.0 + dev: false + + /@noble/hashes@1.6.0: + resolution: {integrity: sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ==} + engines: {node: ^14.21.3 || >=16} + dev: false + + /@noble/hashes@1.6.1: + resolution: {integrity: sha512-pq5D8h10hHBjyqX+cfBm0i8JUXJ0UhczFc4r74zbuT9XgewFo2E3J1cOaGtdZynILNmQ685YWGzGE1Zv6io50w==} + engines: {node: ^14.21.3 || >=16} + dev: false + /@sinclair/typebox@0.27.8: resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} dev: true @@ -1292,6 +1341,11 @@ packages: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} dev: true + /commander@11.1.0: + resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} + engines: {node: '>=16'} + dev: false + /compare-versions@6.1.1: resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==} dev: false @@ -1341,7 +1395,6 @@ packages: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 - dev: true /data-uri-to-buffer@4.0.1: resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} @@ -1411,6 +1464,16 @@ packages: engines: {node: '>=12'} dev: false + /eciesjs@0.4.13: + resolution: {integrity: sha512-zBdtR4K+wbj10bWPpIOF9DW+eFYQu8miU5ypunh0t4Bvt83ZPlEWgT5Dq/0G6uwEXumZKjfb5BZxYUZQ2Hzn/Q==} + engines: {bun: '>=1', deno: '>=2', node: '>=16'} + dependencies: + '@ecies/ciphers': 0.2.2(@noble/ciphers@1.1.3) + '@noble/ciphers': 1.1.3 + '@noble/curves': 1.7.0 + '@noble/hashes': 1.6.1 + dev: false + /ejs@3.1.10: resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==} engines: {node: '>=0.10.0'} @@ -1505,7 +1568,6 @@ packages: onetime: 5.1.2 signal-exit: 3.0.7 strip-final-newline: 2.0.0 - dev: true /exit@0.1.2: resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} @@ -1543,6 +1605,17 @@ packages: bser: 2.1.1 dev: true + /fdir@6.4.2(picomatch@4.0.2): + resolution: {integrity: sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + dependencies: + picomatch: 4.0.2 + dev: false + /fetch-blob@3.2.0: resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} engines: {node: ^12.20 || >= 14.13} @@ -1621,7 +1694,6 @@ packages: /get-stream@6.0.1: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} - dev: true /get-tsconfig@4.8.1: resolution: {integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==} @@ -1677,12 +1749,16 @@ packages: /human-signals@2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} - dev: true /ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} dev: false + /ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + dev: false + /import-local@3.2.0: resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==} engines: {node: '>=8'} @@ -1741,11 +1817,14 @@ packages: /is-stream@2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} - dev: true /isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - dev: true + + /isexe@3.1.1: + resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==} + engines: {node: '>=16'} + dev: false /istanbul-lib-coverage@3.2.2: resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} @@ -2311,7 +2390,6 @@ packages: /merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - dev: true /micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} @@ -2324,7 +2402,6 @@ packages: /mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} - dev: true /mimic-response@3.1.0: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} @@ -2407,7 +2484,11 @@ packages: engines: {node: '>=8'} dependencies: path-key: 3.1.1 - dev: true + + /object-treeify@1.1.33: + resolution: {integrity: sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A==} + engines: {node: '>= 10'} + dev: false /on-exit-leak-free@2.1.2: resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} @@ -2424,7 +2505,6 @@ packages: engines: {node: '>=6'} dependencies: mimic-fn: 2.1.0 - dev: true /p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} @@ -2475,7 +2555,6 @@ packages: /path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} - dev: true /path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} @@ -2490,6 +2569,11 @@ packages: engines: {node: '>=8.6'} dev: true + /picomatch@4.0.2: + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} + engines: {node: '>=12'} + dev: false + /pino-abstract-transport@2.0.0: resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==} dependencies: @@ -2678,16 +2762,13 @@ packages: engines: {node: '>=8'} dependencies: shebang-regex: 3.0.0 - dev: true /shebang-regex@3.0.0: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - dev: true /signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - dev: true /simple-concat@1.0.1: resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} @@ -2782,7 +2863,6 @@ packages: /strip-final-newline@2.0.0: resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} engines: {node: '>=6'} - dev: true /strip-json-comments@2.0.1: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} @@ -3015,7 +3095,14 @@ packages: hasBin: true dependencies: isexe: 2.0.0 - dev: true + + /which@4.0.0: + resolution: {integrity: sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==} + engines: {node: ^16.13.0 || >=18.0.0} + hasBin: true + dependencies: + isexe: 3.1.1 + dev: false /wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} diff --git a/src/actual.ts b/src/actual.ts index fc17dd4..29b621d 100644 --- a/src/actual.ts +++ b/src/actual.ts @@ -3,9 +3,11 @@ import { ACTUAL_DATA_DIR, ACTUAL_PASSWORD, ACTUAL_SERVER_URL, + ACTUAL_SYNC_ID, } from "../config.ts" import type { TransactionEntity } from "@actual-app/api/@types/loot-core/types/models" import { type UUID } from "node:crypto" +import logger from "@/logger.ts" export interface Actual { importTransactions: ( @@ -38,6 +40,9 @@ export class ActualImpl implements Actual { // This is the password you use to log into the server password: ACTUAL_PASSWORD, }) + logger.info(`Initialized ActualBudget API for ${ACTUAL_SERVER_URL}`) + await actual.downloadBudget(ACTUAL_SYNC_ID) + logger.info(`Downloaded budget`) return new ActualImpl() } diff --git a/src/logger.ts b/src/logger.ts new file mode 100644 index 0000000..a85ed1e --- /dev/null +++ b/src/logger.ts @@ -0,0 +1,8 @@ +import pino from "pino" + +/** + * / Returns a logging instance with the default log-level "info" + */ +export default pino({ + level: process.env.LOG_LEVEL as string || "info", +}) diff --git a/src/main.ts b/src/main.ts index 530f30a..7925c6b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -7,7 +7,7 @@ import { } from "@/bank/sparebank1.ts" import { bankTransactionIntoActualTransaction } from "@/mappings.ts" import { ACTUAL_ACCOUNT_IDS, BANK_ACCOUNT_IDS } from "../config.ts" -import logger from "pino" +import logger from "./logger.ts" import type { UUID } from "node:crypto" // TODO Transports api for pino https://github.com/pinojs/pino/blob/HEAD/docs/transports.md @@ -16,7 +16,7 @@ import type { UUID } from "node:crypto" export async function daily(actual: Actual, bank: Bank): Promise { // Fetch transactions from the bank const transactions = await fetchTransactionsFromPastDay(bank) - logger().info(`Fetched ${transactions.length} transactions`) + logger.info(`Fetched ${transactions.length} transactions`) // TODO multiple accounts const accountId = ACTUAL_ACCOUNT_IDS[0] as UUID @@ -24,12 +24,14 @@ export async function daily(actual: Actual, bank: Bank): Promise { bankTransactionIntoActualTransaction(transaction, accountId), ) + logger.debug(`Mapped ${JSON.stringify(transactions)} to ${JSON.stringify(actualTransactions)} transactions`) + // TODO Import transactions into Actual // If multiple accounts, loop over them // Get account ID from mapper - // TODO TypeError: Cannot read properties of undefined (reading 'timestamp') - await actual.importTransactions(accountId, actualTransactions) + const response = await actual.importTransactions(accountId, actualTransactions) + logger.info(`ImportTransactionsResponse=${JSON.stringify(response)}`) } async function fetchTransactionsFromPastDay( @@ -41,17 +43,17 @@ async function fetchTransactionsFromPastDay( } async function main(): Promise { - logger().info("Starting application") + logger.info("Starting application") const actual = await ActualImpl.init() - logger().info("Initialized Actual Budget API") + logger.info("Waiting for CRON job to start") cronJobDaily(async () => { - logger().info("Running daily job") + logger.info("Running daily job") await daily(actual, new Sparebank1Impl()) - logger().info("Finished daily job") + logger.info("Finished daily job") }) - // logger().info("Shutting down") + // logger.info("Shutting down") // await actual.shutdown() } diff --git a/src/mappings.ts b/src/mappings.ts index 4656356..d513af3 100644 --- a/src/mappings.ts +++ b/src/mappings.ts @@ -1,4 +1,4 @@ -import type { Transaction } from "@/sparebank1.ts" +import type { Transaction } from "@/bank/sparebank1.ts" import type { TransactionEntity } from "@actual-app/api/@types/loot-core/types/models" import type { UUID } from "node:crypto" diff --git a/tests/main.test.ts b/tests/main.test.ts index 94f625d..a1b6d09 100644 --- a/tests/main.test.ts +++ b/tests/main.test.ts @@ -1,12 +1,16 @@ -import { describe, expect, it } from "@jest/globals" +import { describe, it } from "@jest/globals" import { daily } from "@/main.ts" import { ActualImpl } from "@/actual.ts" import { BankStub } from "./stubs/bankStub.ts" +// TODO testcontainers with Actual? +// TODO tests don't stop after completing + describe("Main logic of the application", () => { it("should import the transactions to Actual Budget", async () => { - await daily(await ActualImpl.init(), new BankStub()) - expect(true) + const actual = await ActualImpl.init() + await daily(actual, new BankStub()) + await actual.shutdown() }) }) diff --git a/tests/stubs/bankStub.ts b/tests/stubs/bankStub.ts index d782bd3..2d4522e 100644 --- a/tests/stubs/bankStub.ts +++ b/tests/stubs/bankStub.ts @@ -1,4 +1,4 @@ -import type { Bank, OAuthTokenResponse, Transaction } from "@/sparebank1.ts" +import type { Bank, OAuthTokenResponse, Transaction } from "@/bank/sparebank1.ts" const tokenResponse: OAuthTokenResponse = { access_token: "my_access_token", @@ -23,7 +23,7 @@ export class BankStub implements Bank { _accessToken: string, ): Promise> { const someFields = { - date: new Date().toDateString(), + date: "2019-08-20", description: "Test transaction", cleanedDescription: "Test transaction", remoteAccountName: "Test account",