agmission/Development/server/tests/test_active_promos_endpoint.js

135 lines
4.5 KiB
JavaScript

#!/usr/bin/env node
/**
* Test Active Promos Endpoint
*
* Verifies that /api/activePromos returns V2 enhancement fields:
* - priority
* - eligibility
* - durationInMonths
* - chainable
*
* NOTE (v3.0): This test queries the database directly and does NOT test
* the actual HTTP endpoint. For v3.0 authentication and eligibility filtering,
* use test_active_promos_eligibility.js instead.
*/
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++;
}
}
// Load environment
const envPath = path.resolve(process.cwd(), envFile);
require('dotenv').config({ path: envPath });
const connect = require('../helpers/db/connect');
const Settings = require('../model/setting');
const moment = require('moment');
console.log('=== Active Promos Endpoint Test ===\n');
async function testActivePromosEndpoint() {
try {
await connect(false);
console.log('1. Fetching active promos from database...');
const settings = await Settings.findOne({ userId: null }).lean();
const now = moment.utc();
const activePromos = (settings?.subscriptionPromos || [])
.filter(p => {
if (!p.enabled) return false;
const validDate = p.validUntil;
// Include if:
// 1. Has validUntil in the future, OR
// 2. Is a repeating coupon (has durationInMonths) without validUntil (self-expiring)
if (validDate) {
return moment.utc(validDate).isAfter(now);
} else {
return p.durationInMonths && p.durationInMonths > 0;
}
});
console.log(` Total promos in database: ${settings?.subscriptionPromos?.length || 0}`);
console.log(` Active promos: ${activePromos.length}`);
console.log('');
if (activePromos.length === 0) {
console.log('⚠️ No active promos found. Create a test promo with:');
console.log(' - enabled: true');
console.log(' - validUntil: future date');
console.log(' - priority, eligibility, durationInMonths, chainable fields');
return 0;
}
console.log('2. Simulating endpoint response format...');
const responsePromos = activePromos.map(p => ({
type: p.type,
priceKey: p.priceKey,
validUntil: p.validUntil,
name: p.name,
nameKey: p.nameKey,
descriptionKey: p.descriptionKey,
discountType: p.discountType,
discountValue: p.discountValue,
// V2 Enhancement fields
priority: p.priority || 0,
eligibility: p.eligibility || 'all',
durationInMonths: p.durationInMonths,
chainable: p.chainable || false
}));
console.log('');
console.log('3. Verifying V2 fields are included:');
let allHaveV2Fields = true;
responsePromos.forEach((promo, idx) => {
console.log(`\n Promo ${idx + 1}: ${promo.name || 'Unnamed'}`);
console.log(` - priority: ${promo.priority} ${typeof promo.priority === 'number' ? '✓' : '✗'}`);
console.log(` - eligibility: ${promo.eligibility} ${promo.eligibility ? '✓' : '✗'}`);
console.log(` - durationInMonths: ${promo.durationInMonths || 'N/A'} ${promo.durationInMonths ? '✓' : '(optional)'}`);
console.log(` - chainable: ${promo.chainable} ${typeof promo.chainable === 'boolean' ? '✓' : '✗'}`);
if (typeof promo.priority !== 'number' || !promo.eligibility || typeof promo.chainable !== 'boolean') {
allHaveV2Fields = false;
}
});
console.log('');
console.log('4. Security check - couponId should NOT be included:');
const hasCouponId = responsePromos.some(p => p.couponId !== undefined);
console.log(` couponId excluded: ${!hasCouponId ? '✓ YES' : '✗ NO (SECURITY ISSUE!)'}`);
console.log('');
console.log('=== Test Summary ===');
if (allHaveV2Fields && !hasCouponId) {
console.log('✓ All tests passed!');
console.log('Active promos endpoint correctly includes V2 fields without exposing couponId.');
return 0;
} else {
console.log('✗ Test failed!');
if (!allHaveV2Fields) console.log(' - Not all promos have required V2 fields');
if (hasCouponId) console.log(' - couponId is exposed (security issue)');
return 1;
}
} catch (err) {
console.error('ERROR:', err.message);
return 1;
} finally {
process.exit(0);
}
}
testActivePromosEndpoint().then(code => process.exit(code));