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 });