refactor: improve secure-fs throttling configuration and add unit tests

- Enhanced the configureThrottling function to prevent changes to maxConcurrency while operations are in flight.
- Added comprehensive unit tests for secure-fs throttling and retry logic, ensuring correct behavior and configuration.
- Removed outdated secure-fs test file and replaced it with a new, updated version to improve test coverage.
This commit is contained in:
Kacper
2025-12-26 22:06:39 +01:00
parent 6d3314f980
commit ad983c6422
5 changed files with 151 additions and 116 deletions

View File

@@ -1,106 +0,0 @@
/**
* Unit tests for secure-fs throttling and retry logic
*/
import { describe, it, expect, beforeEach, vi } from 'vitest';
import * as secureFs from './secure-fs.js';
describe('secure-fs throttling', () => {
beforeEach(() => {
// Reset throttling configuration before each test
secureFs.configureThrottling({
maxConcurrency: 100,
maxRetries: 3,
baseDelay: 100,
maxDelay: 5000,
});
});
describe('configureThrottling', () => {
it('should update configuration with new values', () => {
secureFs.configureThrottling({ maxConcurrency: 50 });
const config = secureFs.getThrottlingConfig();
expect(config.maxConcurrency).toBe(50);
});
it('should preserve existing values when updating partial config', () => {
secureFs.configureThrottling({ maxRetries: 5 });
const config = secureFs.getThrottlingConfig();
expect(config.maxConcurrency).toBe(100); // Default value preserved
expect(config.maxRetries).toBe(5);
});
});
describe('getThrottlingConfig', () => {
it('should return current configuration', () => {
const config = secureFs.getThrottlingConfig();
expect(config).toHaveProperty('maxConcurrency');
expect(config).toHaveProperty('maxRetries');
expect(config).toHaveProperty('baseDelay');
expect(config).toHaveProperty('maxDelay');
});
it('should return default values initially', () => {
const config = secureFs.getThrottlingConfig();
expect(config.maxConcurrency).toBe(100);
expect(config.maxRetries).toBe(3);
expect(config.baseDelay).toBe(100);
expect(config.maxDelay).toBe(5000);
});
});
describe('getPendingOperations', () => {
it('should return 0 when no operations are pending', () => {
expect(secureFs.getPendingOperations()).toBe(0);
});
});
describe('getActiveOperations', () => {
it('should return 0 when no operations are active', () => {
expect(secureFs.getActiveOperations()).toBe(0);
});
});
describe('concurrency limiting', () => {
it('should limit concurrent operations to maxConcurrency', async () => {
// Configure low concurrency for testing
secureFs.configureThrottling({ maxConcurrency: 2 });
let activeConcurrency = 0;
let maxObservedConcurrency = 0;
const delays: Promise<void>[] = [];
// Create operations that track concurrency
for (let i = 0; i < 10; i++) {
const op = new Promise<void>((resolve) => {
activeConcurrency++;
maxObservedConcurrency = Math.max(maxObservedConcurrency, activeConcurrency);
setTimeout(() => {
activeConcurrency--;
resolve();
}, 10);
});
delays.push(op);
}
// Since we can't directly test internal limiting without mocking fs,
// we verify the configuration is applied correctly
expect(secureFs.getThrottlingConfig().maxConcurrency).toBe(2);
});
});
});
describe('file descriptor error handling', () => {
it('should identify ENFILE as a file descriptor error', () => {
// We test the exported functions behavior, not internal helpers
// The retry logic is tested through integration tests
const config = secureFs.getThrottlingConfig();
expect(config.maxRetries).toBe(3);
});
it('should identify EMFILE as a file descriptor error', () => {
// Same as above - configuration is exposed for monitoring
const config = secureFs.getThrottlingConfig();
expect(config.maxRetries).toBeGreaterThan(0);
});
});

View File

@@ -45,11 +45,18 @@ let fsLimit = pLimit(config.maxConcurrency);
* @param newConfig - Partial configuration to merge with defaults
*/
export function configureThrottling(newConfig: Partial<ThrottleConfig>): void {
config = { ...config, ...newConfig };
// Recreate the limiter if concurrency changed
if (newConfig.maxConcurrency !== undefined) {
fsLimit = pLimit(config.maxConcurrency);
const newConcurrency = newConfig.maxConcurrency;
if (newConcurrency !== undefined && newConcurrency !== config.maxConcurrency) {
if (fsLimit.activeCount > 0 || fsLimit.pendingCount > 0) {
throw new Error(
`[SecureFS] Cannot change maxConcurrency while operations are in flight. Active: ${fsLimit.activeCount}, Pending: ${fsLimit.pendingCount}`
);
}
fsLimit = pLimit(newConcurrency);
}
config = { ...config, ...newConfig };
}
/**