Skip to content
This repository has been archived by the owner on Jul 26, 2023. It is now read-only.

Commit

Permalink
feat: add --cleared option
Browse files Browse the repository at this point in the history
  • Loading branch information
starsprung committed Jan 3, 2021
1 parent 82accea commit 59d2615
Show file tree
Hide file tree
Showing 12 changed files with 196 additions and 78 deletions.
49 changes: 27 additions & 22 deletions README.md

Large diffs are not rendered by default.

11 changes: 6 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"make-dir": "^3.1.0",
"winston": "^3.3.3",
"yargs": "^15.4.1",
"ynab": "^1.21.0"
"ynab-client": "npm:ynab@^1.21.0"
},
"devDependencies": {
"@commitlint/cli": "^11.0.0",
Expand Down
3 changes: 3 additions & 0 deletions src/__mocks__/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ const mocks = {
amazonPassword: jest.fn(() => 'pass123456'),
amazonUsername: jest.fn(() => '[email protected]'),
cacheDir: jest.fn(() => '/path/to/cache/'),
cleared: jest.fn(() => true),
logLevel: jest.fn(() => 'none'),
ynabAccessToken: jest.fn(() => 'f82918ba-4aa7-4805-b9be-fe5e87eaacf3'),
ynabAccountName: jest.fn(() => 'Amazon.com'),
ynabBudgetName: jest.fn(() => 'Budget'),
};

const config = Object.defineProperties(
Expand Down
17 changes: 17 additions & 0 deletions src/__mocks__/ynab-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { SaveTransaction } from 'ynab-client';

export const mocks = {
accounts: {
getAccounts: jest.fn(),
},
budgets: {
getBudgets: jest.fn(),
},
transactions: {
createTransactions: jest.fn(),
},
};

export const API = jest.fn(() => mocks);

export { SaveTransaction };
10 changes: 0 additions & 10 deletions src/__mocks__/ynab.ts

This file was deleted.

16 changes: 15 additions & 1 deletion src/amazon.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
mocks as amazonOrderReportsApiMocks,
} from './__mocks__/amazon-order-reports-api';

jest.mock('amazon-order-reports-api');
jest.mock('inquirer');
jest.mock('./cache');
jest.mock('./config');
Expand Down Expand Up @@ -108,20 +107,23 @@ describe('amazon', () => {
expect(result).toEqual([
{
amount: -21750,
cleared: 'cleared',
date: '2020-01-03',
import_id: '6128e2d42a9b71f2680e46bd90c53fcdb8fe',
memo: 'Some Product',
payee_name: 'This Is A Seller',
},
{
amount: 75980,
cleared: 'cleared',
date: '2020-01-14',
import_id: '9b026229af8f0894ddcd8b575d28859e7357',
memo: '2 x Some kind of coat',
payee_name: 'A Seller',
},
{
amount: 75980,
cleared: 'cleared',
date: '2020-01-14',
import_id: '113728ab6700037b5a466b9b01dd0806f26c',
memo: '2 x Some kind of coat',
Expand All @@ -130,6 +132,18 @@ describe('amazon', () => {
]);
});

it('should respect cleared configuration', async () => {
const config = getConfig();
jest.spyOn(config, 'cleared', 'get').mockReturnValueOnce(false);

const result: Array<AmazonTransaction> = [];
for await (const transaction of getAmazonTransactions()) {
result.push(transaction);
}

expect(result[0]).toHaveProperty('cleared', 'uncleared');
});

it('should use different import_ids for identical items', async () => {
const result: Array<AmazonTransaction> = [];
for await (const transaction of getAmazonTransactions()) {
Expand Down
5 changes: 4 additions & 1 deletion src/amazon.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { AmazonOrderReportsApi, Cookie, LogLevel } from 'amazon-order-reports-api';
import hasha from 'hasha';
import inquirer from 'inquirer';
import { SaveTransaction } from 'ynab';
import { SaveTransaction } from 'ynab-client';
import { readCache, writeCache } from './cache';
import { getConfig } from './config';

Expand Down Expand Up @@ -44,6 +44,9 @@ const createTransation = (
amount: number,
): AmazonTransaction => ({
amount: Math.round(amount * 1000),
cleared: config.cleared
? SaveTransaction.ClearedEnum.Cleared
: SaveTransaction.ClearedEnum.Uncleared,
date: `${date.toISOString().split('T')[0]}`,
import_id: generateImportId(
[type, orderId, date, asinIsbn, title, seller, quantity, amount],
Expand Down
6 changes: 6 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export interface Config {
amazonOtpSecret?: string;
cacheDir: string;
configDir: string;
cleared: boolean;
debugMode: boolean;
logLevel: string;
startDate: string;
Expand Down Expand Up @@ -72,6 +73,11 @@ export const getConfig = (): Config => {
describe: 'Location of config directory',
type: 'string',
})
.option('cleared', {
default: true,
describe: 'Whether transactions should be added as cleared by default',
type: 'boolean',
})
.option('debug-mode', {
default: false,
describe: 'Run internal browser in visible mode and run in slo-mo mode',
Expand Down
15 changes: 2 additions & 13 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
#!/usr/bin/env node

import batch from 'it-batch';
import { API as YnabApi } from 'ynab';
import { getAmazonTransactions } from './amazon';
import { makeCacheDir } from './cache';
import { getConfig } from './config';
import logger from './logger';
import { getAccountId, getBudgetId } from './ynab';

const config = getConfig();
import { addTransactions } from './ynab';

const initialize = async (): Promise<void> => {
await makeCacheDir();
Expand All @@ -17,10 +13,6 @@ const initialize = async (): Promise<void> => {
(async () => {
await initialize();

const budgetId = await getBudgetId(config.ynabBudgetName);
const accountId = await getAccountId(config.ynabBudgetName, config.ynabAccountName);
const ynabApi = new YnabApi(config.ynabAccessToken);

logger.info('Retrieving reports from Amazon');
let count = 0;
for await (const transactionBatch of batch(getAmazonTransactions(), 100)) {
Expand All @@ -30,12 +22,9 @@ const initialize = async (): Promise<void> => {

try {
logger.debug(`Submitting batch of ${transactionBatch.length} transactions`);
await ynabApi.transactions.createTransactions(budgetId, {
transactions: transactionBatch.map((t) => ({ ...t, account_id: accountId })),
});
await addTransactions(transactionBatch);
count += transactionBatch.length;
} catch (err) {
logger.error(`Error during YNAB API call: ${err.error?.detail ?? err.message ?? err.code}`);
break;
}
}
Expand Down
115 changes: 91 additions & 24 deletions src/ynab.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import mockdate from 'mockdate';
import { mocked } from 'ts-jest/utils';
import { readCache, writeCache } from './cache';
import { getAccountId, getBudgetId } from './ynab';
import { mocks as ynabMocks } from './__mocks__/ynab';
import { addTransactions, getAccountId, getBudgetId } from './ynab';
import { mocks as ynabMocks, SaveTransaction } from './__mocks__/ynab-client';

jest.mock('ynab');
jest.mock('./cache');
jest.mock('./config');

Expand Down Expand Up @@ -36,23 +35,23 @@ describe('account', () => {
budgets: [
{
id: budgetId,
name: 'my budget'
}
]
}
name: 'my budget',
},
],
},
});

const result = await getBudgetId('my budget');
expect(mocked(writeCache)).toBeCalledWith(
['f82918ba-4aa7-4805-b9be-fe5e87eaacf3', 'my budget'],
budgetId
budgetId,
);
expect(result).toEqual(budgetId);
});

it('throw error if budget does not exist', async () => {
expect(getAccountId('my budget', 'amazon.com account')).rejects.toThrow(
'Unable to find budget'
'Unable to find budget',
);
});
});
Expand All @@ -65,7 +64,7 @@ describe('account', () => {
const result = await getAccountId('my budget', 'amazon.com account');
expect(mock).toBeCalledWith(
['f82918ba-4aa7-4805-b9be-fe5e87eaacf3', 'my budget', 'amazon.com account'],
3600000
3600000,
);
expect(result).toEqual(accountId);
});
Expand All @@ -79,28 +78,28 @@ describe('account', () => {
budgets: [
{
id: budgetId,
name: 'my budget'
}
]
}
name: 'my budget',
},
],
},
});

ynabMocks.accounts.getAccounts.mockResolvedValueOnce({
data: {
accounts: [
{
id: accountId,
name: 'amazon.com account'
}
]
}
name: 'amazon.com account',
},
],
},
});

const result = await getAccountId('my budget', 'amazon.com account');
expect(ynabMocks.accounts.getAccounts).toBeCalledWith(budgetId);
expect(mocked(writeCache)).toBeCalledWith(
['f82918ba-4aa7-4805-b9be-fe5e87eaacf3', 'my budget', 'amazon.com account'],
accountId
accountId,
);
expect(result).toEqual(accountId);
});
Expand All @@ -113,14 +112,82 @@ describe('account', () => {
budgets: [
{
id: budgetId,
name: 'my budget'
}
]
}
name: 'my budget',
},
],
},
});

expect(getAccountId('my budget', 'amazon.com account')).rejects.toThrow(
'Unable to find account'
'Unable to find account',
);
});
});

describe('addTransactions', () => {
it('should send transactions to YNAB', async () => {
const budgetId = 'f4cc821a-d79a-4c45-86d8-3c477352cbdd';
const accountId = '42ed9b93-4f94-4164-ac88-72eb54d818ed';

ynabMocks.budgets.getBudgets
.mockResolvedValueOnce({
data: {
budgets: [
{
id: budgetId,
name: 'Budget',
},
],
},
})
.mockResolvedValueOnce({
data: {
budgets: [
{
id: budgetId,
name: 'Budget',
},
],
},
});

ynabMocks.accounts.getAccounts.mockResolvedValueOnce({
data: {
accounts: [
{
id: accountId,
name: 'Amazon.com',
},
],
},
});

await addTransactions([
{
amount: 12345,
cleared: SaveTransaction.ClearedEnum.Cleared,
date: '2020-12-31',
import_id: 'b10500b5a731520b3fd242589d6cc0bc4fc1',
memo: 'Very small rocks',
payee_name: 'Amazon.com',
},
]);

expect(ynabMocks.transactions.createTransactions).toBeCalledWith(
'f4cc821a-d79a-4c45-86d8-3c477352cbdd',
{
transactions: [
{
account_id: '42ed9b93-4f94-4164-ac88-72eb54d818ed',
amount: 12345,
cleared: 'cleared',
date: '2020-12-31',
import_id: 'b10500b5a731520b3fd242589d6cc0bc4fc1',
memo: 'Very small rocks',
payee_name: 'Amazon.com',
},
],
},
);
});
});
Expand Down
Loading

0 comments on commit 59d2615

Please sign in to comment.