← back to Whatsmystyle

scripts/test-receipts.js

131 lines

/**
 * Receipt-parser smoke test (tick 14).
 *
 * Sanity-checks that scripts/parse-receipts.js routes each fixture to the
 * right adapter and extracts brand + title + price. No real Gmail calls,
 * no DB writes — pure function-call asserts against inline fixtures.
 *
 * Run:
 *   node scripts/test-receipts.js
 *
 * Exit code is 0 on all-pass, 1 on any failure.
 */
const { parseEmail, pickAdapter } = require('./parse-receipts');

const FIXTURES = [
  {
    name: 'nordstrom',
    email: {
      from: 'orders@emails.nordstrom.com',
      subject: 'Your order has shipped',
      body: [
        'Hi Steve,',
        'Your order has shipped. Items:',
        '',
        'Vince',
        'Crew Neck Cashmere Sweater',
        '$295.00',
        'Size: M',
        '',
        'AGOLDE',
        'Riley High Rise Straight Jean',
        '$198.00',
        'Size: 30',
      ].join('\n'),
    },
    expect: {
      adapter: 'nordstrom',
      minItems: 2,
      brands: ['Vince', 'AGOLDE'],
      titles: ['Crew Neck Cashmere Sweater', 'Riley High Rise Straight Jean'],
      priceCents: [29500, 19800],
    },
  },
  {
    name: 'netaporter',
    email: {
      from: 'orders@emails.net-a-porter.com',
      subject: 'Your NET-A-PORTER order has been dispatched',
      body: [
        'Dear Customer,',
        '',
        'The Row ',
        'Crewneck Cashmere Sweater ',
        'USD 1,290.00',
        '',
        'Khaite ',
        'Stretch Leather Bag ',
        'USD 2,100.00',
      ].join('\n'),
    },
    expect: {
      adapter: 'netaporter',
      minItems: 2,
      brands: ['The Row', 'Khaite'],
      titles: ['Crewneck Cashmere Sweater', 'Stretch Leather Bag'],
      priceCents: [129000, 210000],
    },
  },
  {
    name: 'poshmark',
    email: {
      from: 'no-reply@poshmark.com',
      subject: 'You bought it! Order confirmation',
      body: [
        'You bought it!',
        '',
        'Reformation',
        'Juliette Linen Midi Dress',
        '$118.00',
        '',
        'Acne Studios',
        'Wool Crew Sweater Black',
        '$240.00',
      ].join('\n'),
    },
    expect: {
      adapter: 'poshmark',
      minItems: 2,
      brands: ['Reformation', 'Acne Studios'],
      titles: ['Juliette Linen Midi Dress', 'Wool Crew Sweater Black'],
      priceCents: [11800, 24000],
    },
  },
];

let pass = 0, fail = 0;
const failures = [];

function check(name, condition, detail) {
  if (condition) { pass++; }
  else { fail++; failures.push(`[${name}] ${detail}`); }
}

for (const fx of FIXTURES) {
  const picked = pickAdapter({ from: fx.email.from, subject: fx.email.subject });
  check(fx.name, picked.name === fx.expect.adapter, `expected adapter=${fx.expect.adapter}, got ${picked.name}`);

  const parsed = parseEmail(fx.email);
  check(fx.name, parsed.adapter === fx.expect.adapter, `parsed.adapter mismatch: ${parsed.adapter}`);
  check(fx.name, parsed.items.length >= fx.expect.minItems, `expected ≥${fx.expect.minItems} items, got ${parsed.items.length}`);

  // Assert each expected brand/title/price appears in the parsed items.
  for (let i = 0; i < fx.expect.brands.length; i++) {
    const want = { brand: fx.expect.brands[i], title: fx.expect.titles[i], price_cents: fx.expect.priceCents[i] };
    const hit = parsed.items.find(it =>
      it.brand === want.brand &&
      it.title === want.title &&
      it.price_cents === want.price_cents);
    check(fx.name, !!hit, `missing item: ${JSON.stringify(want)} — got ${JSON.stringify(parsed.items)}`);
  }
}

const out = {
  ok: fail === 0,
  pass, fail,
  failures: failures.slice(0, 20),
  fixtures: FIXTURES.map(f => f.name),
};
console.log(JSON.stringify(out, null, 2));
process.exit(fail === 0 ? 0 : 1);