agmission/Development/server/tests/test_promo_selection_simple.js

340 lines
9.5 KiB
JavaScript

/**
* Simplified Promo Priority Selection Test
*
* Tests priority selection with existing Stripe coupons
* Focuses on match level and priority logic
*/
const path = require('path');
// Load environment
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++;
}
}
const envPath = path.resolve(process.cwd(), envFile);
require('dotenv').config({ path: envPath });
const axios = require('axios');
const assert = require('assert');
const { httpsAgent, sleep, requestWithRetry } = require('./test-helpers');
const BASE_URL = process.env.APP_URL || 'https://localhost:4200';
const API_URL = BASE_URL.replace('4200', '4100');
const ADMIN_USER = 'admin@agnav.com';
const ADMIN_PASSWORD = 'admin';
// Test customer account for invoice preview
const CUSTOMER_EMAIL = 'trungduyhoang@gmail.com';
const CUSTOMER_PASSWORD = 'secret';
// Use existing coupon from your setup
const EXISTING_COUPON = '50OFF';
// Use timestamp for unique promo names to avoid conflicts
const TEST_RUN_ID = Date.now();
const createdPromoIds = []; // Track promos we create for cleanup
let authToken = null;
let customerToken = null;
let customerId = null; // Stripe customer ID
async function login() {
console.log('\n--- Login Admin ---');
const response = await axios.post(`${API_URL}/api/users/login`, {
username: ADMIN_USER,
password: ADMIN_PASSWORD
}, { httpsAgent });
authToken = response.data.token;
console.log('✅ Admin logged in');
}
async function loginCustomer() {
console.log('\n--- Login Customer ---');
const response = await axios.post(`${API_URL}/api/users/login`, {
username: CUSTOMER_EMAIL,
password: CUSTOMER_PASSWORD
}, { httpsAgent });
customerToken = response.data.token;
customerId = response.data.membership?.custId;
console.log('✅ Customer logged in');
console.log(` Stripe Customer ID: ${customerId}`);
if (!customerId) {
throw new Error('Customer does not have a Stripe customer ID. Please create subscription first.');
}
}
/**
* Cleanup only the promos we created in this test run
*/
async function cleanupCreatedPromos() {
if (createdPromoIds.length === 0) {
console.log(' No promos to clean up');
return;
}
console.log(` Cleaning up ${createdPromoIds.length} promos created in this run`);
const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
for (const promoId of createdPromoIds) {
try {
await requestWithRetry('put', `${API_URL}/api/admin/subscriptionPromos/${promoId}`,
{ validUntil: yesterday, enabled: false },
{ headers: { 'Authorization': `Bearer ${authToken}` }, httpsAgent }
);
console.log(` ✓ Disabled promo ${promoId}`);
} catch (error) {
console.log(` ⚠ Could not disable ${promoId}: ${error.message}`);
}
await sleep(100);
}
}
/**
* Create a new promo with unique name
* @param {object} data - Promo data
* @returns {Promise<object>} Created promo
*/
async function createPromo(data) {
// Add unique run ID to name to avoid conflicts
const uniqueData = {
...data,
name: `${data.name}_${TEST_RUN_ID}`
};
const response = await requestWithRetry('post', `${API_URL}/api/admin/subscriptionPromos/add`,
uniqueData,
{ headers: { 'Authorization': `Bearer ${authToken}` }, httpsAgent }
);
// Track created promo ID for cleanup
const promos = response.data.promos || [];
const createdPromo = promos.find(p => p.name === uniqueData.name);
if (createdPromo) {
createdPromoIds.push(createdPromo._id);
}
await sleep(100);
return response.data;
}
async function testInvoicePreview(targetPackage = 'ess_2') {
try {
const response = await axios.post(`${API_URL}/api/subscription/retrieveNextInvoices`,
{
custId: customerId,
package: targetPackage // Use ess_2 to simulate upgrade (customer has ess_1)
},
{ headers: { 'Authorization': `Bearer ${customerToken}` }, httpsAgent }
);
console.log(` DEBUG: Response length: ${response.data?.length}`);
const invoice = response.data?.[0];
console.log(` DEBUG: Invoice discount:`, invoice?.discount);
const discount = invoice?.discount;
return discount ? { applied: true, coupon: discount.coupon?.id } : { applied: false };
} catch (error) {
console.error(' ❌ Invoice preview error:', error.response?.status, error.response?.statusText);
console.error(' ', error.response?.data);
throw error;
}
}
/**
* Test 1: Exact match beats catchall (both same priority)
*/
async function test1_ExactMatchWins() {
console.log('\n' + '='.repeat(60));
console.log('TEST 1: Exact Match Wins Over Catchall');
console.log('='.repeat(60));
// Catchall (priority 0)
await createPromo({
name: 'TEST_Catchall_P0',
couponId: EXISTING_COUPON,
priority: 0,
eligibility: 'all',
validUntil: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString()
});
console.log(' Created catchall (priority 0)');
// Exact match (priority 0)
await createPromo({
name: 'TEST_Exact_P0',
type: 'package',
priceKey: 'ess_2',
couponId: EXISTING_COUPON,
priority: 0,
eligibility: 'all',
validUntil: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString()
});
console.log(' Created exact match (priority 0)');
const result = await testInvoicePreview();
assert(result.applied, 'Should apply promo');
console.log(` ✅ PASS: Promo applied (exact match selected)`);
}
/**
* Test 2: Higher priority wins
*/
async function test2_HigherPriorityWins() {
console.log('\n' + '='.repeat(60));
console.log('TEST 2: Higher Priority Wins');
console.log('='.repeat(60));
// Low priority
await createPromo({
name: 'TEST_Low_P1',
type: 'package',
priceKey: 'ess_2',
couponId: EXISTING_COUPON,
priority: 1,
eligibility: 'all',
validUntil: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString()
});
console.log(' Created low priority (priority 1)');
// High priority
await createPromo({
name: 'TEST_High_P10',
type: 'package',
priceKey: 'ess_2',
couponId: EXISTING_COUPON,
priority: 10,
eligibility: 'all',
validUntil: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString()
});
console.log(' Created high priority (priority 10)');
const result = await testInvoicePreview();
assert(result.applied, 'Should apply promo');
console.log(` ✅ PASS: Higher priority promo selected`);
}
/**
* Test 3: Duration-based promo (no validUntil) is active
*/
async function test3_DurationBasedPromo() {
console.log('\n' + '='.repeat(60));
console.log('TEST 3: Duration-Based Promo (durationInMonths)');
console.log('='.repeat(60));
// Duration-based promo
await createPromo({
name: 'TEST_Duration_12M',
type: 'package',
priceKey: 'ess_2',
couponId: EXISTING_COUPON,
priority: 5,
eligibility: 'all',
durationInMonths: 12
// Note: NO validUntil
});
console.log(' Created duration-based promo (12 months, no validUntil)');
const result = await testInvoicePreview();
assert(result.applied, 'Should apply promo');
console.log(` ✅ PASS: Duration-based promo active without validUntil`);
}
/**
* Test 4: Exact match high priority beats catchall low priority
*/
async function test4_ExactHighBeatsCatchallLow() {
console.log('\n' + '='.repeat(60));
console.log('TEST 4: Exact Match (High) Beats Catchall (Low)');
console.log('='.repeat(60));
// Catchall low priority
await createPromo({
name: 'TEST_Catchall_P0_v2',
couponId: EXISTING_COUPON,
priority: 0,
eligibility: 'all',
validUntil: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString()
});
console.log(' Created catchall (priority 0)');
// Exact match high priority
await createPromo({
name: 'TEST_Exact_P5',
type: 'package',
priceKey: 'ess_2',
couponId: EXISTING_COUPON,
priority: 5,
eligibility: 'all',
durationInMonths: 12
});
console.log(' Created exact match (priority 5, durationInMonths)');
const result = await testInvoicePreview();
assert(result.applied, 'Should apply promo');
console.log(` ✅ PASS: Exact match with higher priority selected`);
}
/**
* Main
*/
async function runTests() {
console.log('='.repeat(60));
console.log('PROMO PRIORITY SELECTION TEST');
console.log('='.repeat(60));
console.log(`API: ${API_URL}`);
console.log(`Coupon: ${EXISTING_COUPON}`);
console.log(`Test Run ID: ${TEST_RUN_ID}`);
try {
await login();
await loginCustomer();
// Run all tests (unique names prevent conflicts)
await test1_ExactMatchWins();
await test2_HigherPriorityWins();
await test3_DurationBasedPromo();
await test4_ExactHighBeatsCatchallLow();
// Cleanup only what we created
console.log('\n' + '='.repeat(60));
console.log('CLEANUP');
console.log('='.repeat(60));
await cleanupCreatedPromos();
console.log('\n' + '='.repeat(60));
console.log('✅ ALL TESTS PASSED (4/4)');
console.log('='.repeat(60));
} catch (error) {
console.error('\n' + '='.repeat(60));
console.error('❌ TEST FAILED');
console.error('='.repeat(60));
console.error(error.response?.data || error.message);
// Try to clean up even on failure
try {
await cleanupCreatedPromos();
} catch (cleanupError) {
console.error('⚠️ Failed to clean up:', cleanupError.message);
}
process.exit(1);
}
}
runTests();