/ tests / payments / paypal-testprice.test.js
paypal-testprice.test.js
  1  /**
  2   * PayPal testPrice Override Tests
  3   *
  4   * Covers uncovered lines 84-87 in src/payment/paypal.js:
  5   * - testPrice override branch in createPaymentOrder()
  6   *
  7   * Run: NODE_ENV=test LOGS_DIR=/tmp/test-logs DATABASE_PATH=/tmp/test-sites.db \
  8   *   node --experimental-test-module-mocks --test tests/payments/paypal-testprice.test.js
  9   */
 10  
 11  import { describe, test, mock, beforeEach } from 'node:test';
 12  import assert from 'node:assert/strict';
 13  
 14  // Mock axios before any imports that use it
 15  const axiosPostMock = mock.fn();
 16  const axiosGetMock = mock.fn();
 17  
 18  mock.module('axios', {
 19    defaultExport: {
 20      post: axiosPostMock,
 21      get: axiosGetMock,
 22    },
 23  });
 24  
 25  // Mock dotenv
 26  mock.module('dotenv', {
 27    defaultExport: { config: () => {} },
 28    namedExports: { config: () => {} },
 29  });
 30  
 31  // Mock country-pricing — returns currencySymbol needed for testPrice branch
 32  mock.module('../../src/utils/country-pricing.js', {
 33    namedExports: {
 34      getPrice: countryCode => {
 35        const pricing = {
 36          US: {
 37            currency: 'USD',
 38            currencySymbol: '$',
 39            priceLocal: 297,
 40            priceUsd: 297,
 41            formattedPrice: '$297.00 USD',
 42            exchangeRate: 1,
 43          },
 44          AU: {
 45            currency: 'AUD',
 46            currencySymbol: 'A$',
 47            priceLocal: 447,
 48            priceUsd: 297,
 49            formattedPrice: 'A$447.00 AUD',
 50            exchangeRate: 1.505,
 51          },
 52        };
 53        return pricing[countryCode] || null;
 54      },
 55    },
 56  });
 57  
 58  // Set env before importing
 59  process.env.PAYPAL_CLIENT_ID = 'test-client-id';
 60  process.env.PAYPAL_CLIENT_SECRET = 'test-client-secret';
 61  process.env.PAYPAL_MODE = 'sandbox';
 62  process.env.PAYPAL_BRAND_NAME = 'Audit&Fix';
 63  process.env.BASE_URL = 'https://test.example.com';
 64  
 65  const { createPaymentOrder } = await import('../../src/payment/paypal.js');
 66  
 67  function mockAccessToken() {
 68    return { data: { access_token: 'mock-token-abc123', expires_in: 3600 } };
 69  }
 70  
 71  function mockOrderResponse(id = 'ORDER_TEST_PRICE') {
 72    return {
 73      data: {
 74        id,
 75        status: 'PAYER_ACTION_REQUIRED',
 76        links: [{ rel: 'payer-action', href: `https://sandbox.paypal.com/pay?token=${id}` }],
 77      },
 78    };
 79  }
 80  
 81  describe('PayPal - testPrice override (lines 84-87)', () => {
 82    beforeEach(() => {
 83      axiosPostMock.mock.resetCalls();
 84      axiosGetMock.mock.resetCalls();
 85      axiosPostMock.mock.mockImplementation(async url => {
 86        if (url.includes('/oauth2/token')) return mockAccessToken();
 87        return mockOrderResponse();
 88      });
 89    });
 90  
 91    test('overrides priceLocal and formattedPrice when testPrice is provided', async () => {
 92      const result = await createPaymentOrder({
 93        domain: 'example.com',
 94        email: 'buyer@test.com',
 95        siteId: 42,
 96        conversationId: 7,
 97        countryCode: 'US',
 98        testPrice: 1.0,
 99      });
100  
101      // Verify the order was created with the overridden price
102      const orderCall = axiosPostMock.mock.calls.find(c =>
103        c.arguments[0].includes('/v2/checkout/orders')
104      );
105      assert.ok(orderCall, 'Order create call should exist');
106      const body = orderCall.arguments[1];
107      assert.equal(body.purchase_units[0].amount.value, '1.00');
108      assert.equal(body.purchase_units[0].amount.currency_code, 'USD');
109  
110      // Result should reflect the overridden price
111      assert.equal(result.amount, 1);
112      assert.equal(result.currency, 'USD');
113    });
114  
115    test('testPrice=0 is treated as a valid override (falsy but not null/undefined)', async () => {
116      const result = await createPaymentOrder({
117        domain: 'example.com',
118        email: 'buyer@test.com',
119        siteId: 1,
120        conversationId: 1,
121        countryCode: 'US',
122        testPrice: 0,
123      });
124  
125      const orderCall = axiosPostMock.mock.calls.find(c =>
126        c.arguments[0].includes('/v2/checkout/orders')
127      );
128      const body = orderCall.arguments[1];
129      assert.equal(body.purchase_units[0].amount.value, '0.00');
130    });
131  
132    test('testPrice with AU country uses AUD currency symbol', async () => {
133      const result = await createPaymentOrder({
134        domain: 'aussie.com.au',
135        email: 'buyer@test.com',
136        siteId: 1,
137        conversationId: 1,
138        countryCode: 'AU',
139        testPrice: 5.5,
140      });
141  
142      const orderCall = axiosPostMock.mock.calls.find(c =>
143        c.arguments[0].includes('/v2/checkout/orders')
144      );
145      const body = orderCall.arguments[1];
146      assert.equal(body.purchase_units[0].amount.value, '5.50');
147      assert.equal(body.purchase_units[0].amount.currency_code, 'AUD');
148      assert.equal(result.amount, 5.5);
149      assert.equal(result.currency, 'AUD');
150    });
151  
152    test('testPrice=null does not override (uses standard pricing)', async () => {
153      const result = await createPaymentOrder({
154        domain: 'example.com',
155        email: 'buyer@test.com',
156        siteId: 1,
157        conversationId: 1,
158        countryCode: 'US',
159        testPrice: null,
160      });
161  
162      const orderCall = axiosPostMock.mock.calls.find(c =>
163        c.arguments[0].includes('/v2/checkout/orders')
164      );
165      const body = orderCall.arguments[1];
166      assert.equal(body.purchase_units[0].amount.value, '297.00');
167    });
168  
169    test('testPrice=undefined does not override (uses standard pricing)', async () => {
170      const result = await createPaymentOrder({
171        domain: 'example.com',
172        email: 'buyer@test.com',
173        siteId: 1,
174        conversationId: 1,
175        countryCode: 'US',
176        testPrice: undefined,
177      });
178  
179      const orderCall = axiosPostMock.mock.calls.find(c =>
180        c.arguments[0].includes('/v2/checkout/orders')
181      );
182      const body = orderCall.arguments[1];
183      assert.equal(body.purchase_units[0].amount.value, '297.00');
184    });
185  });