import { describe, it, expect, jest, beforeAll, afterAll } from '@jest/globals';
import Piscina from 'piscina';
import * as path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
describe('Monte Carlo Worker', () => {
let piscina: Piscina;
beforeAll(() => {
// Create a Piscina instance for the worker
piscina = new Piscina({
filename: path.resolve(__dirname, '../../../src/workers/monte-carlo-worker.js'),
maxThreads: 2,
minThreads: 1
});
});
afterAll(async () => {
await piscina.destroy();
});
describe('Monte Carlo Simulation', () => {
it('should perform simulation with normal distribution', async () => {
const params = {
iterations: 1000,
initialInvestment: 100000,
cashFlows: Array(24).fill(5000), // 24 months of $5000 cash flow
distribution: 'normal' as const,
confidenceLevel: 0.95,
variabilityFactor: 0.15
};
const result = await piscina.run(params);
expect(result).toHaveProperty('mean');
expect(result).toHaveProperty('median');
expect(result).toHaveProperty('stdDev');
expect(result).toHaveProperty('percentiles');
expect(result).toHaveProperty('confidenceInterval');
expect(result).toHaveProperty('probabilityOfSuccess');
// Verify reasonable values
expect(result.mean).toBeGreaterThan(0);
expect(result.probabilityOfSuccess).toBeGreaterThanOrEqual(0);
expect(result.probabilityOfSuccess).toBeLessThanOrEqual(1);
expect(result.confidenceInterval.lower).toBeLessThan(result.confidenceInterval.upper);
});
it('should perform simulation with uniform distribution', async () => {
const params = {
iterations: 1000,
initialInvestment: 50000,
cashFlows: Array(12).fill(6000),
distribution: 'uniform' as const,
confidenceLevel: 0.90,
variabilityFactor: 0.10
};
const result = await piscina.run(params);
expect(result).toHaveProperty('mean');
expect(result).toHaveProperty('median');
expect(result.mean).toBeGreaterThan(0);
// Uniform distribution should have less variance
expect(result.stdDev).toBeLessThan(result.mean * 0.2);
});
it('should perform simulation with beta distribution', async () => {
const params = {
iterations: 1000,
initialInvestment: 75000,
cashFlows: Array(18).fill(7000),
distribution: 'beta' as const,
confidenceLevel: 0.95,
variabilityFactor: 0.20
};
const result = await piscina.run(params);
expect(result).toHaveProperty('mean');
expect(result).toHaveProperty('median');
expect(result).toHaveProperty('percentiles');
// Beta distribution tends to be right-skewed
expect(result.percentiles.p75).toBeGreaterThan(result.median);
});
it('should handle edge case with zero cash flows', async () => {
const params = {
iterations: 100,
initialInvestment: 50000,
cashFlows: Array(12).fill(0),
distribution: 'normal' as const,
confidenceLevel: 0.95,
variabilityFactor: 0.15
};
const result = await piscina.run(params);
expect(result.mean).toBe(-50000); // Only the investment, no returns
expect(result.probabilityOfSuccess).toBe(0);
});
it('should handle high iteration counts efficiently', async () => {
const params = {
iterations: 10000,
initialInvestment: 100000,
cashFlows: Array(36).fill(4000),
distribution: 'normal' as const,
confidenceLevel: 0.95,
variabilityFactor: 0.15
};
const startTime = Date.now();
const result = await piscina.run(params);
const duration = Date.now() - startTime;
expect(result).toHaveProperty('mean');
expect(duration).toBeLessThan(1000); // Should complete within 1 second
});
it('should produce consistent results with same seed', async () => {
const params = {
iterations: 1000,
initialInvestment: 80000,
cashFlows: Array(24).fill(5000),
distribution: 'normal' as const,
confidenceLevel: 0.95,
variabilityFactor: 0.15,
seed: 12345 // Fixed seed
};
const result1 = await piscina.run(params);
const result2 = await piscina.run(params);
// Results should be very close (not exact due to floating point)
expect(Math.abs(result1.mean - result2.mean)).toBeLessThan(0.01);
expect(Math.abs(result1.median - result2.median)).toBeLessThan(0.01);
});
});
describe('Worker Pool Management', () => {
it('should handle multiple concurrent simulations', async () => {
const simulations = Array(5).fill(null).map((_, i) => ({
iterations: 1000,
initialInvestment: 50000 + i * 10000,
cashFlows: Array(24).fill(5000),
distribution: 'normal' as const,
confidenceLevel: 0.95,
variabilityFactor: 0.15
}));
const results = await Promise.all(
simulations.map(params => piscina.run(params))
);
expect(results).toHaveLength(5);
results.forEach(result => {
expect(result).toHaveProperty('mean');
expect(result).toHaveProperty('probabilityOfSuccess');
});
});
it('should respect thread pool limits', async () => {
const poolStats = piscina.threads;
expect(poolStats.length).toBeLessThanOrEqual(2); // maxThreads set to 2
expect(poolStats.length).toBeGreaterThanOrEqual(1); // minThreads set to 1
});
});
describe('Error Handling', () => {
it('should handle invalid distribution type', async () => {
const params = {
iterations: 1000,
initialInvestment: 50000,
cashFlows: Array(12).fill(5000),
distribution: 'invalid' as any,
confidenceLevel: 0.95,
variabilityFactor: 0.15
};
await expect(piscina.run(params)).rejects.toThrow();
});
it('should handle negative iteration count', async () => {
const params = {
iterations: -100,
initialInvestment: 50000,
cashFlows: Array(12).fill(5000),
distribution: 'normal' as const,
confidenceLevel: 0.95,
variabilityFactor: 0.15
};
await expect(piscina.run(params)).rejects.toThrow();
});
it('should handle invalid confidence level', async () => {
const params = {
iterations: 1000,
initialInvestment: 50000,
cashFlows: Array(12).fill(5000),
distribution: 'normal' as const,
confidenceLevel: 1.5, // Invalid: > 1
variabilityFactor: 0.15
};
await expect(piscina.run(params)).rejects.toThrow();
});
});
});