mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-03-22 15:46:33 +01:00
150 lines
5 KiB
JavaScript
150 lines
5 KiB
JavaScript
|
|
const http = require('http');
|
||
|
|
const https = require('https');
|
||
|
|
const { Readable } = require('stream');
|
||
|
|
|
||
|
|
const mockAxios = jest.fn();
|
||
|
|
mockAxios.post = jest.fn();
|
||
|
|
|
||
|
|
jest.mock('@librechat/agents', () => ({
|
||
|
|
getCodeBaseURL: jest.fn(() => 'https://code-api.example.com'),
|
||
|
|
}));
|
||
|
|
|
||
|
|
jest.mock('@librechat/api', () => {
|
||
|
|
const http = require('http');
|
||
|
|
const https = require('https');
|
||
|
|
return {
|
||
|
|
logAxiosError: jest.fn(({ message }) => message),
|
||
|
|
createAxiosInstance: jest.fn(() => mockAxios),
|
||
|
|
codeServerHttpAgent: new http.Agent({ keepAlive: false }),
|
||
|
|
codeServerHttpsAgent: new https.Agent({ keepAlive: false }),
|
||
|
|
};
|
||
|
|
});
|
||
|
|
|
||
|
|
const { codeServerHttpAgent, codeServerHttpsAgent } = require('@librechat/api');
|
||
|
|
const { getCodeOutputDownloadStream, uploadCodeEnvFile } = require('./crud');
|
||
|
|
|
||
|
|
describe('Code CRUD', () => {
|
||
|
|
beforeEach(() => {
|
||
|
|
jest.clearAllMocks();
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('getCodeOutputDownloadStream', () => {
|
||
|
|
it('should pass dedicated keepAlive:false agents to axios', async () => {
|
||
|
|
const mockResponse = { data: Readable.from(['chunk']) };
|
||
|
|
mockAxios.mockResolvedValue(mockResponse);
|
||
|
|
|
||
|
|
await getCodeOutputDownloadStream('session-1/file-1', 'test-key');
|
||
|
|
|
||
|
|
const callConfig = mockAxios.mock.calls[0][0];
|
||
|
|
expect(callConfig.httpAgent).toBe(codeServerHttpAgent);
|
||
|
|
expect(callConfig.httpsAgent).toBe(codeServerHttpsAgent);
|
||
|
|
expect(callConfig.httpAgent).toBeInstanceOf(http.Agent);
|
||
|
|
expect(callConfig.httpsAgent).toBeInstanceOf(https.Agent);
|
||
|
|
expect(callConfig.httpAgent.keepAlive).toBe(false);
|
||
|
|
expect(callConfig.httpsAgent.keepAlive).toBe(false);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should request stream response from the correct URL', async () => {
|
||
|
|
mockAxios.mockResolvedValue({ data: Readable.from(['chunk']) });
|
||
|
|
|
||
|
|
await getCodeOutputDownloadStream('session-1/file-1', 'test-key');
|
||
|
|
|
||
|
|
const callConfig = mockAxios.mock.calls[0][0];
|
||
|
|
expect(callConfig.url).toBe('https://code-api.example.com/download/session-1/file-1');
|
||
|
|
expect(callConfig.responseType).toBe('stream');
|
||
|
|
expect(callConfig.timeout).toBe(15000);
|
||
|
|
expect(callConfig.headers['X-API-Key']).toBe('test-key');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should throw on network error', async () => {
|
||
|
|
mockAxios.mockRejectedValue(new Error('ECONNREFUSED'));
|
||
|
|
|
||
|
|
await expect(getCodeOutputDownloadStream('s/f', 'key')).rejects.toThrow();
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('uploadCodeEnvFile', () => {
|
||
|
|
const baseUploadParams = {
|
||
|
|
req: { user: { id: 'user-123' } },
|
||
|
|
stream: Readable.from(['file-content']),
|
||
|
|
filename: 'data.csv',
|
||
|
|
apiKey: 'test-key',
|
||
|
|
};
|
||
|
|
|
||
|
|
it('should pass dedicated keepAlive:false agents to axios', async () => {
|
||
|
|
mockAxios.post.mockResolvedValue({
|
||
|
|
data: {
|
||
|
|
message: 'success',
|
||
|
|
session_id: 'sess-1',
|
||
|
|
files: [{ fileId: 'fid-1', filename: 'data.csv' }],
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
await uploadCodeEnvFile(baseUploadParams);
|
||
|
|
|
||
|
|
const callConfig = mockAxios.post.mock.calls[0][2];
|
||
|
|
expect(callConfig.httpAgent).toBe(codeServerHttpAgent);
|
||
|
|
expect(callConfig.httpsAgent).toBe(codeServerHttpsAgent);
|
||
|
|
expect(callConfig.httpAgent).toBeInstanceOf(http.Agent);
|
||
|
|
expect(callConfig.httpsAgent).toBeInstanceOf(https.Agent);
|
||
|
|
expect(callConfig.httpAgent.keepAlive).toBe(false);
|
||
|
|
expect(callConfig.httpsAgent.keepAlive).toBe(false);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should set a timeout on upload requests', async () => {
|
||
|
|
mockAxios.post.mockResolvedValue({
|
||
|
|
data: {
|
||
|
|
message: 'success',
|
||
|
|
session_id: 'sess-1',
|
||
|
|
files: [{ fileId: 'fid-1', filename: 'data.csv' }],
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
await uploadCodeEnvFile(baseUploadParams);
|
||
|
|
|
||
|
|
const callConfig = mockAxios.post.mock.calls[0][2];
|
||
|
|
expect(callConfig.timeout).toBe(120000);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should return fileIdentifier on success', async () => {
|
||
|
|
mockAxios.post.mockResolvedValue({
|
||
|
|
data: {
|
||
|
|
message: 'success',
|
||
|
|
session_id: 'sess-1',
|
||
|
|
files: [{ fileId: 'fid-1', filename: 'data.csv' }],
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
const result = await uploadCodeEnvFile(baseUploadParams);
|
||
|
|
expect(result).toBe('sess-1/fid-1');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should append entity_id query param when provided', async () => {
|
||
|
|
mockAxios.post.mockResolvedValue({
|
||
|
|
data: {
|
||
|
|
message: 'success',
|
||
|
|
session_id: 'sess-1',
|
||
|
|
files: [{ fileId: 'fid-1', filename: 'data.csv' }],
|
||
|
|
},
|
||
|
|
});
|
||
|
|
|
||
|
|
const result = await uploadCodeEnvFile({ ...baseUploadParams, entity_id: 'agent-42' });
|
||
|
|
expect(result).toBe('sess-1/fid-1?entity_id=agent-42');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should throw when server returns non-success message', async () => {
|
||
|
|
mockAxios.post.mockResolvedValue({
|
||
|
|
data: { message: 'quota_exceeded', session_id: 's', files: [] },
|
||
|
|
});
|
||
|
|
|
||
|
|
await expect(uploadCodeEnvFile(baseUploadParams)).rejects.toThrow('quota_exceeded');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should throw on network error', async () => {
|
||
|
|
mockAxios.post.mockRejectedValue(new Error('ECONNREFUSED'));
|
||
|
|
|
||
|
|
await expect(uploadCodeEnvFile(baseUploadParams)).rejects.toThrow();
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|