/ tests / payments / paypal-generate-message.test.js
paypal-generate-message.test.js
  1  /**
  2   * Tests for generatePaymentMessage in src/payment/paypal.js
  3   *
  4   * generatePaymentMessage calls getPrice() which queries the countries table.
  5   * We use a dedicated temp DB (overriding DATABASE_PATH) to avoid polluting
  6   * the shared test DB used by other tests.
  7   *
  8   * country-pricing.js reads DATABASE_PATH dynamically on each getDb() call,
  9   * so setting process.env.DATABASE_PATH here takes effect immediately.
 10   */
 11  
 12  import { test, describe, mock, after } from 'node:test';
 13  import assert from 'node:assert/strict';
 14  import { existsSync, unlinkSync } from 'fs';
 15  import { join } from 'path';
 16  import { tmpdir } from 'os';
 17  import Database from 'better-sqlite3';
 18  import { createPgMock } from '../helpers/pg-mock.js';
 19  
 20  // Use a dedicated temp DB to avoid polluting the shared test DB
 21  const TEST_DB = join(tmpdir(), `test-paypal-msg-${Date.now()}.db`);
 22  
 23  // Seed BEFORE mocking db.js
 24  const _pricingDb = new Database(TEST_DB);
 25  _pricingDb.pragma('journal_mode = WAL');
 26  _pricingDb.exec(`
 27    CREATE TABLE IF NOT EXISTS countries (
 28      country_code TEXT PRIMARY KEY,
 29      country_name TEXT NOT NULL,
 30      currency_code TEXT NOT NULL DEFAULT 'USD',
 31      currency_symbol TEXT NOT NULL DEFAULT '$',
 32      price_usd_base INTEGER DEFAULT 29700,
 33      price_usd_ppp INTEGER DEFAULT 29700,
 34      price_local INTEGER DEFAULT 29700,
 35      price_local_formatted TEXT DEFAULT '297',
 36      pricing_tier TEXT DEFAULT 'Standard',
 37      pricing_variant TEXT DEFAULT 'charm',
 38      is_active INTEGER DEFAULT 1,
 39      is_price_sensitive INTEGER DEFAULT 0,
 40      price_overridden INTEGER DEFAULT 0,
 41      override_reason TEXT,
 42      market_notes TEXT,
 43      exchange_rate REAL DEFAULT 1.0,
 44      price_last_updated TEXT,
 45      twilio_phone_number TEXT
 46    )
 47  `);
 48  _pricingDb.prepare(
 49    `INSERT OR IGNORE INTO countries
 50      (country_code, country_name, currency_code, currency_symbol, price_usd_ppp, price_local, price_local_formatted)
 51     VALUES
 52      ('AU', 'Australia', 'AUD', 'A$', 33700, 49700, '497'),
 53      ('US', 'United States', 'USD', '$', 29700, 29700, '297')`
 54  ).run();
 55  
 56  // Mock db.js before importing paypal.js
 57  mock.module('../../src/utils/db.js', {
 58    namedExports: createPgMock(_pricingDb),
 59  });
 60  
 61  const { generatePaymentMessage } = await import('../../src/payment/paypal.js');
 62  
 63  after(() => {
 64    _pricingDb.close();
 65    if (existsSync(TEST_DB)) {
 66      try { unlinkSync(TEST_DB); } catch { /* ignore */ }
 67    }
 68  });
 69  
 70  describe('generatePaymentMessage', () => {
 71    test('returns SMS-formatted message for sms channel', async () => {
 72      const msg = await generatePaymentMessage('https://pay.example.com/abc', 'sms', 'example.com', 'AU');
 73      assert.ok(typeof msg === 'string', 'should return a string');
 74      // SMS messages should be concise
 75      assert.ok(msg.length < 500, 'SMS message should be short');
 76      assert.ok(msg.includes('example.com'), 'should include domain');
 77      assert.ok(msg.includes('https://pay.example.com/abc'), 'should include payment link');
 78    });
 79  
 80    test('returns email-formatted message for email channel', async () => {
 81      const msg = await generatePaymentMessage('https://pay.example.com/abc', 'email', 'mysite.com', 'US');
 82      assert.ok(typeof msg === 'string', 'should return a string');
 83      // Email messages should be longer and more detailed
 84      assert.ok(msg.length > 200, 'email message should be longer');
 85      assert.ok(msg.includes('mysite.com'), 'should include domain');
 86      assert.ok(msg.includes('https://pay.example.com/abc'), 'should include payment link');
 87      assert.ok(
 88        msg.includes('Conversion Audit') || msg.includes('Homepage'),
 89        'should mention conversion audit or homepage'
 90      );
 91    });
 92  
 93    test('includes country-specific pricing for AU', async () => {
 94      const msg = await generatePaymentMessage(
 95        'https://pay.example.com/abc',
 96        'sms',
 97        'example.com.au',
 98        'AU'
 99      );
100      assert.ok(
101        msg.includes('A$') || msg.includes('AUD') || msg.includes('$'),
102        'should include price'
103      );
104    });
105  
106    test('includes country-specific pricing for US', async () => {
107      const msg = await generatePaymentMessage('https://pay.example.com/xyz', 'email', 'example.com', 'US');
108      assert.ok(msg.includes('$'), 'should include dollar sign');
109    });
110  
111    test('handles null country code by defaulting to US', async () => {
112      await assert.doesNotReject(async () => {
113        await generatePaymentMessage('https://pay.example.com/abc', 'sms', 'example.com', null);
114      });
115    });
116  
117    test('handles undefined country code by defaulting to US', async () => {
118      await assert.doesNotReject(async () => {
119        await generatePaymentMessage('https://pay.example.com/abc', 'email', 'example.com', undefined);
120      });
121    });
122  
123    test('non-sms channel returns email format', async () => {
124      const emailMsg = await generatePaymentMessage('https://pay.example.com', 'email', 'site.com', 'AU');
125      const smsMsg = await generatePaymentMessage('https://pay.example.com', 'sms', 'site.com', 'AU');
126  
127      // Email should have newlines and be longer
128      assert.ok(emailMsg.length > smsMsg.length, 'email should be longer than SMS');
129      assert.ok(emailMsg.includes('\n'), 'email should have newlines');
130      assert.ok(!smsMsg.includes('\n\n'), 'SMS should not have double newlines');
131    });
132  
133    test('payment link appears in both SMS and email formats', async () => {
134      const link = 'https://paypal.com/order/TESTORDER123';
135      const smsMsg = await generatePaymentMessage(link, 'sms', 'test.com', 'AU');
136      const emailMsg = await generatePaymentMessage(link, 'email', 'test.com', 'AU');
137  
138      assert.ok(smsMsg.includes(link), 'link should appear in SMS');
139      assert.ok(emailMsg.includes(link), 'link should appear in email');
140    });
141  });