LibreChat/packages/api/src/utils/promise.spec.ts
Theo N. Truong ce7e6edad8
Some checks failed
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Has been cancelled
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Has been cancelled
Docker Dev Images Build / build (Dockerfile, librechat-dev, node) (push) Has been cancelled
Docker Dev Images Build / build (Dockerfile.multi, librechat-dev-api, api-build) (push) Has been cancelled
Sync Locize Translations & Create Translation PR / Sync Translation Keys with Locize (push) Has been cancelled
Sync Locize Translations & Create Translation PR / Create Translation PR on Version Published (push) Has been cancelled
🔄 refactor: MCP Registry System with Distributed Caching (#10191)
* refactor: Restructure MCP registry system with caching

- Split MCPServersRegistry into modular components:
  - MCPServerInspector: handles server inspection and health checks
  - MCPServersInitializer: manages server initialization logic
  - MCPServersRegistry: simplified registry coordination
- Add distributed caching layer:
  - ServerConfigsCacheRedis: Redis-backed configuration cache
  - ServerConfigsCacheInMemory: in-memory fallback cache
  - RegistryStatusCache: distributed leader election state
- Add promise utilities (withTimeout) replacing Promise.race patterns
- Add comprehensive cache integration tests for all cache implementations
- Remove unused MCPManager.getAllToolFunctions method

* fix: Update OAuth flow to include user-specific headers

* chore: Update Jest configuration to ignore additional test files

- Added patterns to ignore files ending with .helper.ts and .helper.d.ts in testPathIgnorePatterns for cleaner test runs.

* fix: oauth headers in callback

* chore: Update Jest testPathIgnorePatterns to exclude helper files

- Modified testPathIgnorePatterns in package.json to ignore files ending with .helper.ts and .helper.d.ts for cleaner test execution.

* ci: update test mocks

---------

Co-authored-by: Danny Avila <danny@librechat.ai>
2025-10-31 15:00:21 -04:00

115 lines
4.1 KiB
TypeScript

import { withTimeout } from './promise';
describe('withTimeout', () => {
beforeEach(() => {
jest.clearAllTimers();
});
it('should resolve when promise completes before timeout', async () => {
const promise = Promise.resolve('success');
const result = await withTimeout(promise, 1000);
expect(result).toBe('success');
});
it('should reject when promise rejects before timeout', async () => {
const promise = Promise.reject(new Error('test error'));
await expect(withTimeout(promise, 1000)).rejects.toThrow('test error');
});
it('should timeout when promise takes too long', async () => {
const promise = new Promise((resolve) => setTimeout(() => resolve('late'), 2000));
await expect(withTimeout(promise, 100, 'Custom timeout message')).rejects.toThrow(
'Custom timeout message',
);
});
it('should use default error message when none provided', async () => {
const promise = new Promise((resolve) => setTimeout(() => resolve('late'), 2000));
await expect(withTimeout(promise, 100)).rejects.toThrow('Operation timed out after 100ms');
});
it('should clear timeout when promise resolves', async () => {
const clearTimeoutSpy = jest.spyOn(global, 'clearTimeout');
const promise = Promise.resolve('fast');
await withTimeout(promise, 1000);
expect(clearTimeoutSpy).toHaveBeenCalled();
clearTimeoutSpy.mockRestore();
});
it('should clear timeout when promise rejects', async () => {
const clearTimeoutSpy = jest.spyOn(global, 'clearTimeout');
const promise = Promise.reject(new Error('fail'));
await expect(withTimeout(promise, 1000)).rejects.toThrow('fail');
expect(clearTimeoutSpy).toHaveBeenCalled();
clearTimeoutSpy.mockRestore();
});
it('should handle multiple concurrent timeouts', async () => {
const promise1 = Promise.resolve('first');
const promise2 = new Promise((resolve) => setTimeout(() => resolve('second'), 50));
const promise3 = new Promise((resolve) => setTimeout(() => resolve('third'), 2000));
const [result1, result2] = await Promise.all([
withTimeout(promise1, 1000),
withTimeout(promise2, 1000),
]);
expect(result1).toBe('first');
expect(result2).toBe('second');
await expect(withTimeout(promise3, 100)).rejects.toThrow('Operation timed out after 100ms');
});
it('should work with async functions', async () => {
const asyncFunction = async () => {
await new Promise((resolve) => setTimeout(resolve, 10));
return 'async result';
};
const result = await withTimeout(asyncFunction(), 1000);
expect(result).toBe('async result');
});
it('should work with any return type', async () => {
const numberPromise = Promise.resolve(42);
const objectPromise = Promise.resolve({ key: 'value' });
const arrayPromise = Promise.resolve([1, 2, 3]);
expect(await withTimeout(numberPromise, 1000)).toBe(42);
expect(await withTimeout(objectPromise, 1000)).toEqual({ key: 'value' });
expect(await withTimeout(arrayPromise, 1000)).toEqual([1, 2, 3]);
});
it('should call logger when timeout occurs', async () => {
const loggerMock = jest.fn();
const promise = new Promise((resolve) => setTimeout(() => resolve('late'), 2000));
const errorMessage = 'Custom timeout with logger';
await expect(withTimeout(promise, 100, errorMessage, loggerMock)).rejects.toThrow(errorMessage);
expect(loggerMock).toHaveBeenCalledTimes(1);
expect(loggerMock).toHaveBeenCalledWith(errorMessage, expect.any(Error));
});
it('should not call logger when promise resolves', async () => {
const loggerMock = jest.fn();
const promise = Promise.resolve('success');
const result = await withTimeout(promise, 1000, 'Should not timeout', loggerMock);
expect(result).toBe('success');
expect(loggerMock).not.toHaveBeenCalled();
});
it('should work without logger parameter', async () => {
const promise = new Promise((resolve) => setTimeout(() => resolve('late'), 2000));
await expect(withTimeout(promise, 100, 'No logger provided')).rejects.toThrow(
'No logger provided',
);
});
});