#!/usr/bin/env node /** * Test Promo Enhancements * * Tests the new promo features: * - Priority-based matching * - Eligibility checking (new_only, renew_only) * - Repeating coupon support * - Subscription history cache * * Usage: * node tests/test_promo_enhancements.js [--env=./environment.env] */ const path = require('path'); // Parse --env argument const args = process.argv.slice(2); let envFile = './environment.env'; for (let i = 0; i < args.length; i++) { if (args[i] === '--env' && args[i + 1]) { envFile = args[i + 1]; i++; } else if (args[i].startsWith('--env=')) { envFile = args[i].split('=')[1]; } } // Load environment const envPath = path.resolve(process.cwd(), envFile); require('dotenv').config({ path: envPath }); const mongoose = require('mongoose'); const connect = require('../helpers/db/connect'); const Settings = require('../model/setting'); const SubscriptionHistory = require('../model/subscription_history');const { PromoEligibility } = require('../helpers/constants');const { stripe } = require('../helpers/subscription_util'); console.log('\n=== Promo Enhancement Tests ===\n'); async function testPromoSchema() { console.log('1. Testing Promo Schema...'); try { const settings = await Settings.findOne({ userId: null }); const testPromo = { type: 'package', priceKey: 'ess_1', enabled: true, validUntil: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000), couponId: 'test_coupon_abc', priority: 10, chainable: false, durationInMonths: 12, eligibility: PromoEligibility.NEW_ONLY, name: 'Test First Year Promo', nameKey: 'TEST_PROMO', discountType: 'percent', discountValue: 30, usageCount: 0 }; console.log(' ✓ Test promo object structure valid'); console.log(` - priority: ${testPromo.priority}`); console.log(` - eligibility: ${testPromo.eligibility}`); console.log(` - durationInMonths: ${testPromo.durationInMonths}`); console.log(` - chainable: ${testPromo.chainable}`); return true; } catch (err) { console.error(' ✗ Schema test failed:', err.message); return false; } } async function testSubscriptionHistoryCache() { console.log('\n2. Testing Subscription History Cache...'); try { // Create test history record const testHistory = { custId: 'cus_test_123', type: 'package', priceKey: 'ess_1', firstSubscribedAt: new Date('2025-01-01'), lastSubscribedAt: new Date(), totalSubscriptions: 2, currentSubscriptionId: 'sub_test_active', lastSubscriptionStatus: 'active', lastSyncedAt: new Date() }; await SubscriptionHistory.findOneAndUpdate( { custId: testHistory.custId, type: testHistory.type, priceKey: testHistory.priceKey }, testHistory, { upsert: true, new: true } ); console.log(' ✓ History cache record created'); // Query history const found = await SubscriptionHistory.findOne({ custId: 'cus_test_123', type: 'package', priceKey: 'ess_1' }).lean(); if (found) { console.log(` ✓ History cache query successful`); console.log(` - firstSubscribedAt: ${found.firstSubscribedAt.toISOString()}`); console.log(` - totalSubscriptions: ${found.totalSubscriptions}`); console.log(` - currentSubscriptionId: ${found.currentSubscriptionId}`); console.log(` - lastSubscriptionStatus: ${found.lastSubscriptionStatus}`); } else { throw new Error('History record not found'); } // Cleanup await SubscriptionHistory.deleteOne({ custId: 'cus_test_123' }); console.log(' ✓ Cleanup complete'); return true; } catch (err) { console.error(' ✗ History cache test failed:', err.message); return false; } } async function testPriorityMatching() { console.log('\n3. Testing Priority-Based Matching...'); try { const promos = [ { type: 'package', priceKey: 'ess_1', priority: 5, name: 'Standard Promo', enabled: true, validUntil: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000) }, { type: 'package', priceKey: 'ess_1', priority: 10, name: 'Premium Promo', enabled: true, validUntil: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000) } ]; // Sort by priority (descending) promos.sort((a, b) => (b.priority || 0) - (a.priority || 0)); if (promos[0].name === 'Premium Promo') { console.log(' ✓ Priority sorting works correctly'); console.log(` - Winner: ${promos[0].name} (priority: ${promos[0].priority})`); } else { throw new Error('Priority sorting failed'); } return true; } catch (err) { console.error(' ✗ Priority matching test failed:', err.message); return false; } } async function testEligibilityLogic() { console.log('\n4. Testing Eligibility Logic...'); try { // Simulate eligibility checks const scenarios = [ { eligibility: PromoEligibility.ALL, hasHistory: true, expected: true }, { eligibility: PromoEligibility.ALL, hasHistory: false, expected: true }, { eligibility: PromoEligibility.NEW_ONLY, hasHistory: false, expected: true }, { eligibility: PromoEligibility.NEW_ONLY, hasHistory: true, expected: false }, { eligibility: PromoEligibility.RENEW_ONLY, hasHistory: true, expected: true }, { eligibility: PromoEligibility.RENEW_ONLY, hasHistory: false, expected: false } ]; let passed = 0; for (const scenario of scenarios) { let isEligible; if (scenario.eligibility === PromoEligibility.ALL) { isEligible = true; } else if (scenario.eligibility === PromoEligibility.NEW_ONLY) { isEligible = !scenario.hasHistory; } else if (scenario.eligibility === PromoEligibility.RENEW_ONLY) { isEligible = scenario.hasHistory; } if (isEligible === scenario.expected) { passed++; } else { console.error(` ✗ Failed: ${scenario.eligibility} + hasHistory=${scenario.hasHistory} → expected ${scenario.expected}, got ${isEligible}`); } } console.log(` ✓ Eligibility logic: ${passed}/${scenarios.length} scenarios passed`); return passed === scenarios.length; } catch (err) { console.error(' ✗ Eligibility test failed:', err.message); return false; } } async function testCouponDurationSupport() { console.log('\n5. Testing Coupon Duration Support...'); try { if (!stripe) { console.log(' ⊘ Stripe not configured - skipping coupon validation test'); return true; } // Test validation logic const validDurations = ['forever', 'repeating']; const invalidDurations = ['once']; console.log(` ✓ Valid durations: ${validDurations.join(', ')}`); console.log(` ✓ Invalid durations: ${invalidDurations.join(', ')}`); // Simulate duration check for (const duration of validDurations) { const isValid = ['forever', 'repeating'].includes(duration); if (!isValid) { throw new Error(`Duration '${duration}' should be valid but failed check`); } } for (const duration of invalidDurations) { const isValid = ['forever', 'repeating'].includes(duration); if (isValid) { throw new Error(`Duration '${duration}' should be invalid but passed check`); } } console.log(' ✓ Duration validation logic correct'); return true; } catch (err) { console.error(' ✗ Coupon duration test failed:', err.message); return false; } } async function main() { try { await connect(false); console.log('Connected to MongoDB\n'); const results = []; results.push(await testPromoSchema()); results.push(await testSubscriptionHistoryCache()); results.push(await testPriorityMatching()); results.push(await testEligibilityLogic()); results.push(await testCouponDurationSupport()); const passed = results.filter(r => r).length; const total = results.length; console.log('\n=== Test Summary ==='); console.log(`Passed: ${passed}/${total}`); console.log(`Failed: ${total - passed}/${total}`); if (passed === total) { console.log('\n✓ All tests passed!\n'); } else { console.log('\n✗ Some tests failed\n'); process.exit(1); } } catch (err) { console.error('\nFATAL ERROR:', err); process.exit(1); } finally { await mongoose.connection.close(); process.exit(0); } } main();