@dbs-portal/tool-mock
Version:
API mocking toolkit using MSW for DBS Portal development workflows
264 lines • 7.63 kB
JavaScript
/**
* Network and performance simulation utilities
*/
import { getRandom } from '../factories/base';
import { applyDelay } from './delay';
/**
* Simulate network conditions
*/
export async function simulateNetworkConditions(options) {
const { connection, packetLoss, latency, bandwidth } = options;
// Simulate packet loss
if (packetLoss && getRandom() < packetLoss) {
throw new Error('Packet loss simulated');
}
// Apply connection-based latency
switch (connection) {
case 'fast':
await applyDelay(latency || [10, 50]);
break;
case 'slow':
await applyDelay(latency || [1000, 3000]);
break;
case 'unstable':
await applyDelay(latency || [100, 2000]);
break;
case 'offline':
throw new Error('Network offline');
default:
await applyDelay(latency || [100, 300]);
}
// Simulate bandwidth limitations (simplified)
if (bandwidth) {
const dataSize = 1024; // Assume 1KB response
const transferTime = (dataSize / bandwidth) * 1000; // Convert to ms
await applyDelay(transferTime);
}
}
/**
* Simulate server load
*/
export async function simulateServerLoad(loadFactor = 0.5, baseDelay = 100) {
// Higher load = longer delays
const loadDelay = baseDelay * (1 + loadFactor * 2);
await applyDelay([loadDelay * 0.8, loadDelay * 1.2]);
}
/**
* Simulate database query time
*/
export async function simulateDatabaseQuery(complexity = 'simple', recordCount = 100) {
let baseTime;
switch (complexity) {
case 'simple':
baseTime = 10;
break;
case 'medium':
baseTime = 50;
break;
case 'complex':
baseTime = 200;
break;
default:
baseTime = 50;
}
// Scale with record count (logarithmically)
const scaledTime = baseTime * Math.log10(recordCount + 1);
await applyDelay([scaledTime * 0.5, scaledTime * 1.5]);
}
/**
* Simulate file processing time
*/
export async function simulateFileProcessing(fileSize, // in bytes
processingType = 'upload') {
const sizeInMB = fileSize / (1024 * 1024);
let timePerMB;
switch (processingType) {
case 'upload':
timePerMB = 500; // 500ms per MB
break;
case 'download':
timePerMB = 200; // 200ms per MB
break;
case 'transform':
timePerMB = 1000; // 1s per MB
break;
default:
timePerMB = 500;
}
const processingTime = sizeInMB * timePerMB;
await applyDelay([processingTime * 0.8, processingTime * 1.2]);
}
/**
* Simulate cache behavior
*/
export function simulateCache(key, factory, ttl = 60000 // 1 minute default
) {
const cache = getCacheInstance();
const cached = cache.get(key);
if (cached && Date.now() - cached.timestamp < ttl) {
return cached.value;
}
const value = factory();
cache.set(key, { value, timestamp: Date.now() });
return value;
}
/**
* Simple in-memory cache
*/
const cacheStore = new Map();
function getCacheInstance() {
return {
get: (key) => cacheStore.get(key),
set: (key, value) => {
cacheStore.set(key, value);
},
clear: () => cacheStore.clear(),
delete: (key) => cacheStore.delete(key),
};
}
/**
* Simulate authentication check delay
*/
export async function simulateAuthCheck(tokenComplexity = 'jwt') {
switch (tokenComplexity) {
case 'simple':
await applyDelay([5, 15]);
break;
case 'jwt':
await applyDelay([20, 50]);
break;
case 'oauth':
await applyDelay([100, 300]);
break;
default:
await applyDelay([20, 50]);
}
}
/**
* Simulate rate limiting
*/
export class RateLimiter {
limit;
windowMs;
requests = new Map();
constructor(limit = 100, windowMs = 60000 // 1 minute
) {
this.limit = limit;
this.windowMs = windowMs;
}
isAllowed(identifier) {
const now = Date.now();
const windowStart = now - this.windowMs;
// Get existing requests for this identifier
const requests = this.requests.get(identifier) || [];
// Filter out old requests
const recentRequests = requests.filter(time => time > windowStart);
// Check if limit exceeded
if (recentRequests.length >= this.limit) {
return false;
}
// Add current request
recentRequests.push(now);
this.requests.set(identifier, recentRequests);
return true;
}
getRemainingRequests(identifier) {
const now = Date.now();
const windowStart = now - this.windowMs;
const requests = this.requests.get(identifier) || [];
const recentRequests = requests.filter(time => time > windowStart);
return Math.max(0, this.limit - recentRequests.length);
}
getResetTime(identifier) {
const requests = this.requests.get(identifier) || [];
if (requests.length === 0) {
return Date.now();
}
return Math.min(...requests) + this.windowMs;
}
}
/**
* Simulate circuit breaker pattern
*/
export class CircuitBreaker {
failureThreshold;
recoveryTimeMs;
failures = 0;
lastFailureTime = 0;
state = 'closed';
constructor(failureThreshold = 5, recoveryTimeMs = 60000 // 1 minute
) {
this.failureThreshold = failureThreshold;
this.recoveryTimeMs = recoveryTimeMs;
}
async execute(operation) {
if (this.state === 'open') {
if (Date.now() - this.lastFailureTime > this.recoveryTimeMs) {
this.state = 'half-open';
}
else {
throw new Error('Circuit breaker is open');
}
}
try {
const result = await operation();
this.onSuccess();
return result;
}
catch (error) {
this.onFailure();
throw error;
}
}
onSuccess() {
this.failures = 0;
this.state = 'closed';
}
onFailure() {
this.failures++;
this.lastFailureTime = Date.now();
if (this.failures >= this.failureThreshold) {
this.state = 'open';
}
}
getState() {
return this.state;
}
}
/**
* Simulate progressive backoff
*/
export async function simulateBackoff(attempt, baseDelay = 1000, maxDelay = 30000, factor = 2) {
const delay = Math.min(baseDelay * Math.pow(factor, attempt - 1), maxDelay);
const jitter = delay * 0.1 * getRandom(); // Add 10% jitter
await applyDelay(delay + jitter);
}
/**
* Simulate health check
*/
export function simulateHealthCheck() {
const checks = {
database: getRandom() > 0.05, // 95% healthy
cache: getRandom() > 0.02, // 98% healthy
externalApi: getRandom() > 0.1, // 90% healthy
storage: getRandom() > 0.01, // 99% healthy
};
const healthyCount = Object.values(checks).filter(Boolean).length;
const totalChecks = Object.keys(checks).length;
let status;
if (healthyCount === totalChecks) {
status = 'healthy';
}
else if (healthyCount >= totalChecks * 0.7) {
status = 'degraded';
}
else {
status = 'unhealthy';
}
return {
status,
checks,
timestamp: new Date().toISOString(),
};
}
//# sourceMappingURL=simulation.js.map