npm-api-analyzer
Version:
CLI tool to analyze npm packages for network API usage, prototype pollution, and security vulnerabilities
260 lines (210 loc) • 9.13 kB
text/typescript
import { APIDetector } from '../src/apiDetector';
describe('Performance Tests', () => {
const measureTime = (fn: () => void): number => {
const start = Date.now();
fn();
return Date.now() - start;
};
const generateTestCode = (iterations: number, pattern: string): string => {
return Array(iterations).fill(pattern).join('\n');
};
describe('Pattern Detection Performance', () => {
test('should handle large files efficiently', () => {
const largeCode = generateTestCode(1000, 'Object.prototype.test = true;');
const duration = measureTime(() => {
APIDetector.detectAPIUsages(largeCode, 'large-test.js');
});
// Should complete within reasonable time (less than 100ms for 1000 patterns)
expect(duration).toBeLessThan(100);
});
test('should handle mixed patterns efficiently', () => {
const mixedPatterns = [
'fetch("/api/data");',
'Object.prototype.polluted = true;',
'eval("malicious");',
'document.createElement("div");',
'obj.__proto__ = evil;',
'for (let k in user) { target[k] = user[k]; }',
'JSON.parse(\'{"__proto__": {}}\');',
'new WebSocket("ws://test");'
];
const largeCode = Array(100).fill(mixedPatterns.join('\n')).join('\n');
const duration = measureTime(() => {
const results = APIDetector.detectAPIUsages(largeCode, 'mixed-test.js');
expect(results.length).toBeGreaterThan(0);
});
// Should handle 800 mixed patterns efficiently
expect(duration).toBeLessThan(50);
});
test('should maintain performance with repetitive calls', () => {
const testCode = `
Object.prototype.admin = true;
fetch('/api/user');
eval('dangerous');
obj.__proto__ = malicious;
`;
const iterations = 100;
const durations: number[] = [];
for (let i = 0; i < iterations; i++) {
const duration = measureTime(() => {
APIDetector.detectAPIUsages(testCode, `test-${i}.js`);
});
durations.push(duration);
}
const avgDuration = durations.reduce((a, b) => a + b, 0) / durations.length;
const maxDuration = Math.max(...durations);
// Average should be very fast
expect(avgDuration).toBeLessThan(2);
// No single call should be too slow
expect(maxDuration).toBeLessThan(10);
});
});
describe('Memory Usage Tests', () => {
test('should not cause memory leaks with repeated calls', () => {
const testCode = 'Object.prototype.test = "value";';
const initialMemory = process.memoryUsage().heapUsed;
// Run many iterations
for (let i = 0; i < 1000; i++) {
APIDetector.detectAPIUsages(testCode, `memory-test-${i}.js`);
}
// Force garbage collection if available
if (global.gc) {
global.gc();
}
const finalMemory = process.memoryUsage().heapUsed;
const memoryIncrease = finalMemory - initialMemory;
// Memory increase should be reasonable (less than 10MB)
expect(memoryIncrease).toBeLessThan(10 * 1024 * 1024);
});
test('should handle very long single lines', () => {
const longLine = 'const data = "' + 'x'.repeat(10000) + '"; Object.prototype.test = data;';
const duration = measureTime(() => {
const results = APIDetector.detectAPIUsages(longLine, 'long-line-test.js');
expect(results.length).toBeGreaterThan(0);
});
// Should handle long lines without significant performance impact
expect(duration).toBeLessThan(20);
});
});
describe('Regex Pattern Efficiency', () => {
test('should not have exponential backtracking issues', () => {
// Create patterns that could cause catastrophic backtracking
const problematicPatterns = [
'Object.prototype.' + 'a'.repeat(1000) + ' = value;',
'obj["' + 'b'.repeat(1000) + '"] = test;',
'for (let ' + 'c'.repeat(100) + ' in data) { target[key] = value; }'
];
problematicPatterns.forEach((pattern, index) => {
const duration = measureTime(() => {
APIDetector.detectAPIUsages(pattern, `backtracking-test-${index}.js`);
});
// Should complete quickly even with complex patterns
expect(duration).toBeLessThan(10);
});
});
});
describe('Scalability Tests', () => {
test('should scale linearly with input size', () => {
const basePattern = 'Object.prototype.test = true;\n';
const sizes = [100, 500, 1000];
const durations: number[] = [];
sizes.forEach(size => {
const code = generateTestCode(size, basePattern);
const duration = measureTime(() => {
APIDetector.detectAPIUsages(code, `scale-test-${size}.js`);
});
durations.push(duration);
});
// Performance should scale reasonably (not exponentially)
// Larger input should not be dramatically slower per pattern
const perPatternTimes = durations.map((duration, i) => Math.max(duration, 1) / sizes[i]);
const maxTime = Math.max(...perPatternTimes);
const minTime = Math.min(...perPatternTimes);
const ratio = minTime > 0 ? maxTime / minTime : 1;
expect(ratio).toBeLessThan(10); // Performance shouldn't degrade more than 10x
});
test('should handle diverse file types and structures', () => {
const testFiles = [
// Minified code
'fetch("/api");eval("x");Object.prototype.a=1;for(let k in u)t[k]=u[k];',
// Well-formatted code
`
class SecurityTest {
constructor() {
this.api = fetch('/test');
}
dangerousMethod() {
Object.prototype.polluted = true;
eval('malicious code');
}
}
`,
// Mixed patterns with comments
`
/* This file contains various security patterns */
// Network request
const response = fetch('/api/data');
// Prototype pollution
Object.prototype.isAdmin = true;
// Dynamic execution
setTimeout('alert("xss")', 1000);
`
];
testFiles.forEach((code, index) => {
const duration = measureTime(() => {
const results = APIDetector.detectAPIUsages(code, `diverse-test-${index}.js`);
expect(results.length).toBeGreaterThan(0);
});
expect(duration).toBeLessThan(10);
});
});
});
describe('Throughput Benchmarks', () => {
test('should achieve high detection throughput', () => {
const testCode = `
Object.prototype.admin = true;
fetch('/api/user');
eval('dangerous');
obj.__proto__ = malicious;
for (let k in user) { obj[k] = user[k]; }
`;
const iterations = 1000;
const startTime = Date.now();
let totalDetections = 0;
for (let i = 0; i < iterations; i++) {
const results = APIDetector.detectAPIUsages(testCode, `throughput-test-${i}.js`);
totalDetections += results.length;
}
const duration = Date.now() - startTime;
const detectionsPerSecond = (totalDetections / duration) * 1000;
// Should achieve high throughput (>10,000 detections per second)
expect(detectionsPerSecond).toBeGreaterThan(10000);
console.log(`Throughput: ${Math.round(detectionsPerSecond).toLocaleString()} detections/second`);
console.log(`Total detections: ${totalDetections.toLocaleString()}`);
console.log(`Duration: ${duration}ms`);
});
test('should maintain consistent performance under load', () => {
const testCode = 'Object.prototype.test = true; fetch("/api"); eval("code");';
const batchSize = 100;
const batches = 10;
const batchTimes: number[] = [];
for (let batch = 0; batch < batches; batch++) {
const batchStart = Date.now();
for (let i = 0; i < batchSize; i++) {
APIDetector.detectAPIUsages(testCode, `load-test-${batch}-${i}.js`);
}
const batchTime = Date.now() - batchStart;
batchTimes.push(Math.max(batchTime, 1)); // Ensure minimum 1ms to avoid division by zero
}
const avgBatchTime = batchTimes.reduce((a, b) => a + b, 0) / batchTimes.length;
const maxBatchTime = Math.max(...batchTimes);
const minBatchTime = Math.min(...batchTimes);
// Performance should be consistent across batches
const variabilityRatio = maxBatchTime / minBatchTime;
expect(variabilityRatio).toBeLessThan(10); // More lenient threshold
console.log(`Average batch time: ${avgBatchTime}ms`);
console.log(`Min/Max batch time: ${minBatchTime}ms / ${maxBatchTime}ms`);
console.log(`Variability ratio: ${variabilityRatio.toFixed(2)}x`);
});
});
});