340 lines
9.5 KiB
JavaScript
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();
|