← back to AbramsOS

tests/csv-parser.test.js

86 lines

const test = require('node:test');
const assert = require('node:assert');
const { parseStatement, parseAmount, parseDate } = require('../lib/csv-parser');

test('parses standard amex-style export', () => {
  const csv = `Date,Description,Amount
01/15/2026,"AMAZON.COM, INC",24.99
01/16/2026,WHOLE FOODS,87.42
01/17/2026,STARBUCKS,5.50`;
  const r = parseStatement(csv);
  assert.strictEqual(r.rows.length, 3);
  assert.strictEqual(r.rows[0].merchant, 'AMAZON.COM, INC');
  assert.strictEqual(r.rows[0].amount, 24.99);
  assert.strictEqual(r.rows[1].merchant, 'WHOLE FOODS');
  assert.strictEqual(r.skipped, 0);
});

test('parses chase-style with extra columns', () => {
  const csv = `Transaction Date,Post Date,Description,Category,Type,Amount,Memo
12/01/2025,12/02/2025,APPLE.COM/BILL,Shopping,Sale,9.99,
12/05/2025,12/06/2025,SHELL OIL,Gas,Sale,42.00,`;
  const r = parseStatement(csv);
  assert.strictEqual(r.rows.length, 2);
  assert.strictEqual(r.rows[0].merchant, 'APPLE.COM/BILL');
});

test('skips refund rows (negative amounts)', () => {
  const csv = `Date,Description,Amount
01/01/2026,RETURN BEST BUY,-50.00
01/02/2026,COFFEE SHOP,4.50`;
  const r = parseStatement(csv);
  assert.strictEqual(r.rows.length, 1);
  assert.strictEqual(r.rows[0].merchant, 'COFFEE SHOP');
  assert.strictEqual(r.skipped, 1);
});

test('handles dollar signs and commas in amounts', () => {
  const csv = `Date,Description,Amount
01/01/2026,FANCY HOTEL,"$1,234.56"`;
  const r = parseStatement(csv);
  assert.strictEqual(r.rows[0].amount, 1234.56);
});

test('handles parentheses for negatives (rejected as refund)', () => {
  const csv = `Date,Description,Amount
01/01/2026,REFUND,(45.00)`;
  const r = parseStatement(csv);
  assert.strictEqual(r.rows.length, 0);
  assert.strictEqual(r.skipped, 1);
});

test('rejects when required columns missing', () => {
  const csv = `Foo,Bar
1,2`;
  const r = parseStatement(csv);
  assert.strictEqual(r.rows.length, 0);
  assert.match(r.errors[0], /missing required columns/);
});

test('rejects empty input cleanly', () => {
  const r = parseStatement('');
  assert.strictEqual(r.rows.length, 0);
  assert.ok(r.errors.length >= 1);
});

test('parseAmount edge cases', () => {
  assert.strictEqual(parseAmount('$1,234.56'), 1234.56);
  assert.strictEqual(parseAmount('(99.00)'), -99);
  assert.strictEqual(parseAmount(''), null);
  assert.strictEqual(parseAmount('not-a-number'), null);
});

test('parseDate handles common US + ISO formats', () => {
  assert.ok(parseDate('2026-01-15') instanceof Date);
  assert.ok(parseDate('01/15/2026') instanceof Date);
  assert.ok(parseDate('1/15/26') instanceof Date);
  assert.strictEqual(parseDate(''), null);
});

test('quoted fields with embedded commas', () => {
  const csv = `Date,Description,Amount
01/01/2026,"Smith, John & Associates",100.00`;
  const r = parseStatement(csv);
  assert.strictEqual(r.rows[0].merchant, 'Smith, John & Associates');
});