const axios = require('axios');
const BASE_URL = 'http://localhost:3000/api';
const DOCS_URL = 'http://localhost:3000/api-docs';
// Colors for terminal output
const colors = {
reset: '\x1b[0m',
green: '\x1b[32m',
red: '\x1b[31m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
cyan: '\x1b[36m'
};
let testsPassed = 0;
let testsFailed = 0;
/**
* Test helper functions
*/
const log = (message, color = 'reset') => {
console.log(`${colors[color]}${message}${colors.reset}`);
};
const testResult = (testName, passed, details = '') => {
if (passed) {
testsPassed++;
log(`ā
${testName}`, 'green');
if (details) log(` ${details}`, 'cyan');
} else {
testsFailed++;
log(`ā ${testName}`, 'red');
if (details) log(` ${details}`, 'yellow');
}
};
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
/**
* Test 1: Security Headers (Helmet)
*/
async function testSecurityHeaders() {
log('\nš Test 1: Security Headers', 'blue');
try {
const response = await axios.get(`${BASE_URL}/categories`);
const headers = response.headers;
// Check for essential security headers
const hasXContentTypeOptions = headers['x-content-type-options'] === 'nosniff';
const hasXFrameOptions = headers['x-frame-options'] === 'DENY';
const hasXXssProtection = headers['x-xss-protection'] === '1; mode=block' || !headers['x-xss-protection']; // Optional (deprecated)
const hasStrictTransportSecurity = headers['strict-transport-security']?.includes('max-age');
const noPoweredBy = !headers['x-powered-by'];
testResult(
'Security headers present',
hasXContentTypeOptions && hasXFrameOptions && hasStrictTransportSecurity && noPoweredBy,
`X-Content-Type: ${hasXContentTypeOptions}, X-Frame: ${hasXFrameOptions}, HSTS: ${hasStrictTransportSecurity}, No X-Powered-By: ${noPoweredBy}`
);
} catch (error) {
testResult('Security headers present', false, error.message);
}
}
/**
* Test 2: Rate Limiting - General API
*/
async function testApiRateLimit() {
log('\nš Test 2: API Rate Limiting (100 req/15min)', 'blue');
try {
// Make multiple requests to test rate limiting
const requests = [];
for (let i = 0; i < 5; i++) {
requests.push(axios.get(`${BASE_URL}/categories`));
}
const responses = await Promise.all(requests);
const firstResponse = responses[0];
// Check for rate limit headers
const hasRateLimitHeaders =
firstResponse.headers['ratelimit-limit'] &&
firstResponse.headers['ratelimit-remaining'] !== undefined;
testResult(
'API rate limit headers present',
hasRateLimitHeaders,
`Limit: ${firstResponse.headers['ratelimit-limit']}, Remaining: ${firstResponse.headers['ratelimit-remaining']}`
);
} catch (error) {
testResult('API rate limit headers present', false, error.message);
}
}
/**
* Test 3: Rate Limiting - Login Endpoint
*/
async function testLoginRateLimit() {
log('\nš Test 3: Login Rate Limiting (5 req/15min)', 'blue');
try {
// Attempt multiple login requests
const requests = [];
for (let i = 0; i < 6; i++) {
requests.push(
axios.post(`${BASE_URL}/auth/login`, {
email: 'test@example.com',
password: 'wrongpassword'
}).catch(err => err.response)
);
await delay(100); // Small delay between requests
}
const responses = await Promise.all(requests);
const rateLimited = responses.some(r => r && r.status === 429);
testResult(
'Login rate limit enforced',
rateLimited,
rateLimited ? 'Rate limit triggered after multiple attempts' : 'May need more requests to trigger'
);
} catch (error) {
testResult('Login rate limit enforced', false, error.message);
}
}
/**
* Test 4: NoSQL Injection Protection
*/
async function testNoSQLInjection() {
log('\nš Test 4: NoSQL Injection Protection', 'blue');
try {
// Attempt NoSQL injection in login
const response = await axios.post(`${BASE_URL}/auth/login`, {
email: { $gt: '' },
password: { $gt: '' }
}).catch(err => err.response);
// Should either get 400 validation error or sanitized input (not 200)
const protected = response.status !== 200;
testResult(
'NoSQL injection prevented',
protected,
`Status: ${response.status} - ${response.data.message || 'Input sanitized'}`
);
} catch (error) {
testResult('NoSQL injection prevented', false, error.message);
}
}
/**
* Test 5: XSS Protection
*/
async function testXSSProtection() {
log('\nš Test 5: XSS Protection', 'blue');
try {
// Attempt XSS in registration
const xssPayload = '';
const response = await axios.post(`${BASE_URL}/auth/register`, {
username: xssPayload,
email: 'xss@test.com',
password: 'Password123!'
}).catch(err => err.response);
// Should either reject or sanitize
const responseData = JSON.stringify(response.data);
const sanitized = !responseData.includes('