pricing-supplement.test.js
1 /** 2 * Supplement tests for src/config/pricing.js 3 * 4 * Covers uncovered exported functions: 5 * - getLocalPrice: returns formatted price object 6 * - getPricingTierDistribution: groups countries by tier 7 * - getPriceStatistics: min/max/average/median 8 * - overrideCountryPrice: mutates COUNTRY_PRICES; throws for unknown code 9 */ 10 11 import { test, describe } from 'node:test'; 12 import assert from 'node:assert/strict'; 13 import { 14 getCountryPrice, 15 getLocalPrice, 16 getPricingTierDistribution, 17 getPriceStatistics, 18 overrideCountryPrice, 19 COUNTRY_PRICES, 20 PRICING_CONFIG, 21 } from '../../src/config/pricing.js'; 22 23 describe('getCountryPrice', () => { 24 test('returns known country price for AU', () => { 25 const result = getCountryPrice('AU'); 26 assert.equal(result.usdPrice, 247); 27 assert.equal(result.tier, 'Standard'); 28 }); 29 30 test('case-insensitive: au === AU', () => { 31 const upper = getCountryPrice('AU'); 32 const lower = getCountryPrice('au'); 33 assert.equal(upper.usdPrice, lower.usdPrice); 34 }); 35 36 test('returns base price for unknown country code', () => { 37 const result = getCountryPrice('ZZ'); 38 assert.equal(result.usdPrice, PRICING_CONFIG.BASE_PRICE_USD); 39 assert.equal(result.tier, 'Standard'); 40 }); 41 42 test('returns Premium+ for SG (capped at ceiling)', () => { 43 const result = getCountryPrice('SG'); 44 assert.equal(result.tier, 'Premium+'); 45 assert.ok(result.cappedAtCeiling); 46 }); 47 }); 48 49 describe('getLocalPrice', () => { 50 test('returns amount, currency, currencySymbol, formatted, usdEquivalent for AU', () => { 51 const { usdPrice } = getCountryPrice('AU'); 52 const countryConfig = { currency: 'AUD', currencySymbol: 'A$' }; 53 54 const result = getLocalPrice('AU', countryConfig); 55 56 assert.equal(result.amount, usdPrice); 57 assert.equal(result.currency, 'AUD'); 58 assert.equal(result.currencySymbol, 'A$'); 59 assert.ok(result.formatted.includes('A$')); 60 assert.ok(result.formatted.includes(String(usdPrice))); 61 assert.equal(result.usdEquivalent, usdPrice); 62 }); 63 64 test('works for US (baseline market)', () => { 65 const result = getLocalPrice('US', { currency: 'USD', currencySymbol: '$' }); 66 assert.equal(result.amount, 297); 67 assert.equal(result.formatted, '$297'); 68 }); 69 70 test('works for unknown country (uses base price)', () => { 71 const result = getLocalPrice('ZZ', { currency: 'USD', currencySymbol: '$' }); 72 assert.equal(result.amount, PRICING_CONFIG.BASE_PRICE_USD); 73 }); 74 }); 75 76 describe('getPricingTierDistribution', () => { 77 test('returns object with tier keys', () => { 78 const dist = getPricingTierDistribution(); 79 assert.ok(typeof dist === 'object'); 80 assert.ok(Array.isArray(dist['Premium+'])); 81 assert.ok(Array.isArray(dist['Premium'])); 82 assert.ok(Array.isArray(dist['Standard'])); 83 assert.ok(Array.isArray(dist['Moderate'])); 84 assert.ok(Array.isArray(dist['Emerging'])); 85 assert.ok(Array.isArray(dist['Developing'])); 86 }); 87 88 test('Premium+ tier contains SG and IE', () => { 89 const dist = getPricingTierDistribution(); 90 const codes = dist['Premium+'].map(e => e.code); 91 assert.ok(codes.includes('SG'), 'SG should be Premium+'); 92 assert.ok(codes.includes('IE'), 'IE should be Premium+'); 93 }); 94 95 test('all countries are distributed (total count matches COUNTRY_PRICES length)', () => { 96 const dist = getPricingTierDistribution(); 97 const total = Object.values(dist).reduce((sum, arr) => sum + arr.length, 0); 98 assert.equal(total, Object.keys(COUNTRY_PRICES).length); 99 }); 100 101 test('each entry has code and price fields', () => { 102 const dist = getPricingTierDistribution(); 103 for (const arr of Object.values(dist)) { 104 for (const entry of arr) { 105 assert.ok('code' in entry, 'entry should have code'); 106 assert.ok('price' in entry, 'entry should have price'); 107 } 108 } 109 }); 110 }); 111 112 describe('getPriceStatistics', () => { 113 test('returns min, max, average, median, basePrice, floor, ceiling', () => { 114 const stats = getPriceStatistics(); 115 assert.ok(typeof stats.min === 'number'); 116 assert.ok(typeof stats.max === 'number'); 117 assert.ok(typeof stats.average === 'number'); 118 assert.ok(typeof stats.median === 'number'); 119 assert.ok(typeof stats.basePrice === 'number'); 120 assert.ok(typeof stats.floor === 'number'); 121 assert.ok(typeof stats.ceiling === 'number'); 122 }); 123 124 test('min <= average <= max', () => { 125 const stats = getPriceStatistics(); 126 assert.ok(stats.min <= stats.average, 'min should be <= average'); 127 assert.ok(stats.average <= stats.max, 'average should be <= max'); 128 }); 129 130 test('max <= ceiling', () => { 131 const stats = getPriceStatistics(); 132 assert.ok(stats.max <= stats.ceiling, 'max should be <= ceiling'); 133 }); 134 135 test('basePrice matches PRICING_CONFIG.BASE_PRICE_USD', () => { 136 const stats = getPriceStatistics(); 137 assert.equal(stats.basePrice, PRICING_CONFIG.BASE_PRICE_USD); 138 }); 139 }); 140 141 describe('overrideCountryPrice', () => { 142 test('throws for unknown country code', () => { 143 assert.throws(() => overrideCountryPrice('ZZ', 199, 'test override'), /Unknown country code/); 144 }); 145 146 test('mutates price for known country', () => { 147 const originalPrice = COUNTRY_PRICES['IN'].usdPrice; 148 const originalNotes = COUNTRY_PRICES['IN'].notes; 149 150 overrideCountryPrice('IN', 99, 'price test override'); 151 152 assert.equal(COUNTRY_PRICES['IN'].usdPrice, 99); 153 assert.ok(COUNTRY_PRICES['IN'].notes.includes('OVERRIDE')); 154 assert.ok(COUNTRY_PRICES['IN'].overridden); 155 156 // Restore original value 157 COUNTRY_PRICES['IN'].usdPrice = originalPrice; 158 COUNTRY_PRICES['IN'].notes = originalNotes; 159 COUNTRY_PRICES['IN'].overridden = false; 160 }); 161 162 test('accepts lowercase country code', () => { 163 const originalPrice = COUNTRY_PRICES['MX'].usdPrice; 164 const originalNotes = COUNTRY_PRICES['MX'].notes; 165 166 overrideCountryPrice('mx', 79, 'lowercase test'); 167 168 assert.equal(COUNTRY_PRICES['MX'].usdPrice, 79); 169 170 // Restore 171 COUNTRY_PRICES['MX'].usdPrice = originalPrice; 172 COUNTRY_PRICES['MX'].notes = originalNotes; 173 COUNTRY_PRICES['MX'].overridden = false; 174 }); 175 });