mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-16 16:30:15 +01:00
📎 fix: Document Uploads for Custom Endpoints (#10336)
* Fixed upload to provider for custom endpoints + unit tests * fix: add support back for agents to be able to use Upload to Provider with supported providers * ci: add test for agents endpoint still recognizing document supported providers * chore: address ESLint suggestions * Improved unit tests * Linting error on unit tests fixed --------- Co-authored-by: Dustin Healy <dustinhealy1@gmail.com>
This commit is contained in:
parent
ce7e6edad8
commit
14e4941367
4 changed files with 728 additions and 3 deletions
|
|
@ -117,8 +117,10 @@ const AttachFileMenu = ({
|
|||
const items: MenuItemProps[] = [];
|
||||
|
||||
const currentProvider = provider || endpoint;
|
||||
|
||||
if (isDocumentSupportedProvider(currentProvider || endpointType)) {
|
||||
if (
|
||||
isDocumentSupportedProvider(endpointType) ||
|
||||
isDocumentSupportedProvider(currentProvider)
|
||||
) {
|
||||
items.push({
|
||||
label: localize('com_ui_upload_provider'),
|
||||
onClick: () => {
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ const DragDropModal = ({ onOptionSelect, setShowModal, files, isVisible }: DragD
|
|||
const currentProvider = provider || endpoint;
|
||||
|
||||
// Check if provider supports document upload
|
||||
if (isDocumentSupportedProvider(currentProvider || endpointType)) {
|
||||
if (isDocumentSupportedProvider(endpointType) || isDocumentSupportedProvider(currentProvider)) {
|
||||
const isGoogleProvider = currentProvider === EModelEndpoint.google;
|
||||
const validFileTypes = isGoogleProvider
|
||||
? files.every(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,602 @@
|
|||
import React from 'react';
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { EModelEndpoint } from 'librechat-data-provider';
|
||||
import AttachFileMenu from '../AttachFileMenu';
|
||||
|
||||
// Mock all the hooks
|
||||
jest.mock('~/hooks', () => ({
|
||||
useAgentToolPermissions: jest.fn(),
|
||||
useAgentCapabilities: jest.fn(),
|
||||
useGetAgentsConfig: jest.fn(),
|
||||
useFileHandling: jest.fn(),
|
||||
useLocalize: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('~/hooks/Files/useSharePointFileHandling', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('~/data-provider', () => ({
|
||||
useGetStartupConfig: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('~/components/SharePoint', () => ({
|
||||
SharePointPickerDialog: jest.fn(() => null),
|
||||
}));
|
||||
|
||||
jest.mock('@librechat/client', () => {
|
||||
const React = jest.requireActual('react');
|
||||
return {
|
||||
FileUpload: React.forwardRef(({ children, handleFileChange }: any, ref: any) => (
|
||||
<div data-testid="file-upload">
|
||||
<input ref={ref} type="file" onChange={handleFileChange} data-testid="file-input" />
|
||||
{children}
|
||||
</div>
|
||||
)),
|
||||
TooltipAnchor: ({ render }: any) => render,
|
||||
DropdownPopup: ({ trigger, items, isOpen, setIsOpen }: any) => {
|
||||
const handleTriggerClick = () => {
|
||||
if (setIsOpen) {
|
||||
setIsOpen(!isOpen);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div onClick={handleTriggerClick}>{trigger}</div>
|
||||
{isOpen && (
|
||||
<div data-testid="dropdown-menu">
|
||||
{items.map((item: any, idx: number) => (
|
||||
<button key={idx} onClick={item.onClick} data-testid={`menu-item-${idx}`}>
|
||||
{item.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
AttachmentIcon: () => <span data-testid="attachment-icon">📎</span>,
|
||||
SharePointIcon: () => <span data-testid="sharepoint-icon">SP</span>,
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('@ariakit/react', () => ({
|
||||
MenuButton: ({ children, onClick, disabled, ...props }: any) => (
|
||||
<button onClick={onClick} disabled={disabled} {...props}>
|
||||
{children}
|
||||
</button>
|
||||
),
|
||||
}));
|
||||
|
||||
const mockUseAgentToolPermissions = jest.requireMock('~/hooks').useAgentToolPermissions;
|
||||
const mockUseAgentCapabilities = jest.requireMock('~/hooks').useAgentCapabilities;
|
||||
const mockUseGetAgentsConfig = jest.requireMock('~/hooks').useGetAgentsConfig;
|
||||
const mockUseFileHandling = jest.requireMock('~/hooks').useFileHandling;
|
||||
const mockUseLocalize = jest.requireMock('~/hooks').useLocalize;
|
||||
const mockUseSharePointFileHandling = jest.requireMock(
|
||||
'~/hooks/Files/useSharePointFileHandling',
|
||||
).default;
|
||||
const mockUseGetStartupConfig = jest.requireMock('~/data-provider').useGetStartupConfig;
|
||||
|
||||
describe('AttachFileMenu', () => {
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: { retry: false },
|
||||
},
|
||||
});
|
||||
|
||||
const mockHandleFileChange = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
// Default mock implementations
|
||||
mockUseLocalize.mockReturnValue((key: string) => {
|
||||
const translations: Record<string, string> = {
|
||||
com_ui_upload_provider: 'Upload to Provider',
|
||||
com_ui_upload_image_input: 'Upload Image',
|
||||
com_ui_upload_ocr_text: 'Upload OCR Text',
|
||||
com_ui_upload_file_search: 'Upload for File Search',
|
||||
com_ui_upload_code_files: 'Upload Code Files',
|
||||
com_sidepanel_attach_files: 'Attach Files',
|
||||
com_files_upload_sharepoint: 'Upload from SharePoint',
|
||||
};
|
||||
return translations[key] || key;
|
||||
});
|
||||
|
||||
mockUseAgentCapabilities.mockReturnValue({
|
||||
contextEnabled: false,
|
||||
fileSearchEnabled: false,
|
||||
codeEnabled: false,
|
||||
});
|
||||
|
||||
mockUseGetAgentsConfig.mockReturnValue({
|
||||
agentsConfig: {
|
||||
capabilities: {
|
||||
contextEnabled: false,
|
||||
fileSearchEnabled: false,
|
||||
codeEnabled: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
mockUseFileHandling.mockReturnValue({
|
||||
handleFileChange: mockHandleFileChange,
|
||||
});
|
||||
|
||||
mockUseSharePointFileHandling.mockReturnValue({
|
||||
handleSharePointFiles: jest.fn(),
|
||||
isProcessing: false,
|
||||
downloadProgress: 0,
|
||||
});
|
||||
|
||||
mockUseGetStartupConfig.mockReturnValue({
|
||||
data: {
|
||||
sharePointFilePickerEnabled: false,
|
||||
},
|
||||
});
|
||||
|
||||
mockUseAgentToolPermissions.mockReturnValue({
|
||||
fileSearchAllowedByAgent: false,
|
||||
codeAllowedByAgent: false,
|
||||
provider: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
const renderAttachFileMenu = (props: any = {}) => {
|
||||
return render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<RecoilRoot>
|
||||
<AttachFileMenu conversationId="test-conversation" {...props} />
|
||||
</RecoilRoot>
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
};
|
||||
|
||||
describe('Basic Rendering', () => {
|
||||
it('should render the attachment button', () => {
|
||||
renderAttachFileMenu();
|
||||
const button = screen.getByRole('button', { name: /attach file options/i });
|
||||
expect(button).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should be disabled when disabled prop is true', () => {
|
||||
renderAttachFileMenu({ disabled: true });
|
||||
const button = screen.getByRole('button', { name: /attach file options/i });
|
||||
expect(button).toBeDisabled();
|
||||
});
|
||||
|
||||
it('should not be disabled when disabled prop is false', () => {
|
||||
renderAttachFileMenu({ disabled: false });
|
||||
const button = screen.getByRole('button', { name: /attach file options/i });
|
||||
expect(button).not.toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Provider Detection Fix - endpointType Priority', () => {
|
||||
it('should prioritize endpointType over currentProvider for LiteLLM gateway', () => {
|
||||
mockUseAgentToolPermissions.mockReturnValue({
|
||||
fileSearchAllowedByAgent: false,
|
||||
codeAllowedByAgent: false,
|
||||
provider: 'litellm', // Custom gateway name NOT in documentSupportedProviders
|
||||
});
|
||||
|
||||
renderAttachFileMenu({
|
||||
endpoint: 'litellm',
|
||||
endpointType: EModelEndpoint.openAI, // Backend override IS in documentSupportedProviders
|
||||
});
|
||||
|
||||
const button = screen.getByRole('button', { name: /attach file options/i });
|
||||
fireEvent.click(button);
|
||||
|
||||
// With the fix, should show "Upload to Provider" because endpointType is checked first
|
||||
expect(screen.getByText('Upload to Provider')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Upload Image')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show Upload to Provider for custom endpoints with OpenAI endpointType', () => {
|
||||
mockUseAgentToolPermissions.mockReturnValue({
|
||||
fileSearchAllowedByAgent: false,
|
||||
codeAllowedByAgent: false,
|
||||
provider: 'my-custom-gateway',
|
||||
});
|
||||
|
||||
renderAttachFileMenu({
|
||||
endpoint: 'my-custom-gateway',
|
||||
endpointType: EModelEndpoint.openAI,
|
||||
});
|
||||
|
||||
const button = screen.getByRole('button', { name: /attach file options/i });
|
||||
fireEvent.click(button);
|
||||
|
||||
expect(screen.getByText('Upload to Provider')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show Upload Image when neither endpointType nor provider support documents', () => {
|
||||
mockUseAgentToolPermissions.mockReturnValue({
|
||||
fileSearchAllowedByAgent: false,
|
||||
codeAllowedByAgent: false,
|
||||
provider: 'unsupported-provider',
|
||||
});
|
||||
|
||||
renderAttachFileMenu({
|
||||
endpoint: 'unsupported-provider',
|
||||
endpointType: 'unsupported-endpoint' as any,
|
||||
});
|
||||
|
||||
const button = screen.getByRole('button', { name: /attach file options/i });
|
||||
fireEvent.click(button);
|
||||
|
||||
expect(screen.getByText('Upload Image')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Upload to Provider')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should fallback to currentProvider when endpointType is undefined', () => {
|
||||
mockUseAgentToolPermissions.mockReturnValue({
|
||||
fileSearchAllowedByAgent: false,
|
||||
codeAllowedByAgent: false,
|
||||
provider: EModelEndpoint.openAI,
|
||||
});
|
||||
|
||||
renderAttachFileMenu({
|
||||
endpoint: EModelEndpoint.openAI,
|
||||
endpointType: undefined,
|
||||
});
|
||||
|
||||
const button = screen.getByRole('button', { name: /attach file options/i });
|
||||
fireEvent.click(button);
|
||||
|
||||
expect(screen.getByText('Upload to Provider')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should fallback to currentProvider when endpointType is null', () => {
|
||||
mockUseAgentToolPermissions.mockReturnValue({
|
||||
fileSearchAllowedByAgent: false,
|
||||
codeAllowedByAgent: false,
|
||||
provider: EModelEndpoint.anthropic,
|
||||
});
|
||||
|
||||
renderAttachFileMenu({
|
||||
endpoint: EModelEndpoint.anthropic,
|
||||
endpointType: null,
|
||||
});
|
||||
|
||||
const button = screen.getByRole('button', { name: /attach file options/i });
|
||||
fireEvent.click(button);
|
||||
|
||||
expect(screen.getByText('Upload to Provider')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Supported Providers', () => {
|
||||
const supportedProviders = [
|
||||
{ name: 'OpenAI', endpoint: EModelEndpoint.openAI },
|
||||
{ name: 'Anthropic', endpoint: EModelEndpoint.anthropic },
|
||||
{ name: 'Google', endpoint: EModelEndpoint.google },
|
||||
{ name: 'Azure OpenAI', endpoint: EModelEndpoint.azureOpenAI },
|
||||
{ name: 'Custom', endpoint: EModelEndpoint.custom },
|
||||
];
|
||||
|
||||
supportedProviders.forEach(({ name, endpoint }) => {
|
||||
it(`should show Upload to Provider for ${name}`, () => {
|
||||
mockUseAgentToolPermissions.mockReturnValue({
|
||||
fileSearchAllowedByAgent: false,
|
||||
codeAllowedByAgent: false,
|
||||
provider: endpoint,
|
||||
});
|
||||
|
||||
renderAttachFileMenu({
|
||||
endpoint,
|
||||
endpointType: endpoint,
|
||||
});
|
||||
|
||||
const button = screen.getByRole('button', { name: /attach file options/i });
|
||||
fireEvent.click(button);
|
||||
|
||||
expect(screen.getByText('Upload to Provider')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Agent Capabilities', () => {
|
||||
it('should show OCR Text option when context is enabled', () => {
|
||||
mockUseAgentCapabilities.mockReturnValue({
|
||||
contextEnabled: true,
|
||||
fileSearchEnabled: false,
|
||||
codeEnabled: false,
|
||||
});
|
||||
|
||||
renderAttachFileMenu({
|
||||
endpointType: EModelEndpoint.openAI,
|
||||
});
|
||||
|
||||
const button = screen.getByRole('button', { name: /attach file options/i });
|
||||
fireEvent.click(button);
|
||||
|
||||
expect(screen.getByText('Upload OCR Text')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show File Search option when enabled and allowed by agent', () => {
|
||||
mockUseAgentCapabilities.mockReturnValue({
|
||||
contextEnabled: false,
|
||||
fileSearchEnabled: true,
|
||||
codeEnabled: false,
|
||||
});
|
||||
|
||||
mockUseAgentToolPermissions.mockReturnValue({
|
||||
fileSearchAllowedByAgent: true,
|
||||
codeAllowedByAgent: false,
|
||||
provider: undefined,
|
||||
});
|
||||
|
||||
renderAttachFileMenu({
|
||||
endpointType: EModelEndpoint.openAI,
|
||||
});
|
||||
|
||||
const button = screen.getByRole('button', { name: /attach file options/i });
|
||||
fireEvent.click(button);
|
||||
|
||||
expect(screen.getByText('Upload for File Search')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should NOT show File Search when enabled but not allowed by agent', () => {
|
||||
mockUseAgentCapabilities.mockReturnValue({
|
||||
contextEnabled: false,
|
||||
fileSearchEnabled: true,
|
||||
codeEnabled: false,
|
||||
});
|
||||
|
||||
mockUseAgentToolPermissions.mockReturnValue({
|
||||
fileSearchAllowedByAgent: false,
|
||||
codeAllowedByAgent: false,
|
||||
provider: undefined,
|
||||
});
|
||||
|
||||
renderAttachFileMenu({
|
||||
endpointType: EModelEndpoint.openAI,
|
||||
});
|
||||
|
||||
const button = screen.getByRole('button', { name: /attach file options/i });
|
||||
fireEvent.click(button);
|
||||
|
||||
expect(screen.queryByText('Upload for File Search')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show Code Files option when enabled and allowed by agent', () => {
|
||||
mockUseAgentCapabilities.mockReturnValue({
|
||||
contextEnabled: false,
|
||||
fileSearchEnabled: false,
|
||||
codeEnabled: true,
|
||||
});
|
||||
|
||||
mockUseAgentToolPermissions.mockReturnValue({
|
||||
fileSearchAllowedByAgent: false,
|
||||
codeAllowedByAgent: true,
|
||||
provider: undefined,
|
||||
});
|
||||
|
||||
renderAttachFileMenu({
|
||||
endpointType: EModelEndpoint.openAI,
|
||||
});
|
||||
|
||||
const button = screen.getByRole('button', { name: /attach file options/i });
|
||||
fireEvent.click(button);
|
||||
|
||||
expect(screen.getByText('Upload Code Files')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show all options when all capabilities are enabled', () => {
|
||||
mockUseAgentCapabilities.mockReturnValue({
|
||||
contextEnabled: true,
|
||||
fileSearchEnabled: true,
|
||||
codeEnabled: true,
|
||||
});
|
||||
|
||||
mockUseAgentToolPermissions.mockReturnValue({
|
||||
fileSearchAllowedByAgent: true,
|
||||
codeAllowedByAgent: true,
|
||||
provider: undefined,
|
||||
});
|
||||
|
||||
renderAttachFileMenu({
|
||||
endpointType: EModelEndpoint.openAI,
|
||||
});
|
||||
|
||||
const button = screen.getByRole('button', { name: /attach file options/i });
|
||||
fireEvent.click(button);
|
||||
|
||||
expect(screen.getByText('Upload to Provider')).toBeInTheDocument();
|
||||
expect(screen.getByText('Upload OCR Text')).toBeInTheDocument();
|
||||
expect(screen.getByText('Upload for File Search')).toBeInTheDocument();
|
||||
expect(screen.getByText('Upload Code Files')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('SharePoint Integration', () => {
|
||||
it('should show SharePoint option when enabled', () => {
|
||||
mockUseGetStartupConfig.mockReturnValue({
|
||||
data: {
|
||||
sharePointFilePickerEnabled: true,
|
||||
},
|
||||
});
|
||||
|
||||
renderAttachFileMenu({
|
||||
endpointType: EModelEndpoint.openAI,
|
||||
});
|
||||
|
||||
const button = screen.getByRole('button', { name: /attach file options/i });
|
||||
fireEvent.click(button);
|
||||
|
||||
expect(screen.getByText('Upload from SharePoint')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should NOT show SharePoint option when disabled', () => {
|
||||
mockUseGetStartupConfig.mockReturnValue({
|
||||
data: {
|
||||
sharePointFilePickerEnabled: false,
|
||||
},
|
||||
});
|
||||
|
||||
renderAttachFileMenu({
|
||||
endpointType: EModelEndpoint.openAI,
|
||||
});
|
||||
|
||||
const button = screen.getByRole('button', { name: /attach file options/i });
|
||||
fireEvent.click(button);
|
||||
|
||||
expect(screen.queryByText('Upload from SharePoint')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge Cases', () => {
|
||||
it('should handle undefined endpoint and provider gracefully', () => {
|
||||
mockUseAgentToolPermissions.mockReturnValue({
|
||||
fileSearchAllowedByAgent: false,
|
||||
codeAllowedByAgent: false,
|
||||
provider: undefined,
|
||||
});
|
||||
|
||||
renderAttachFileMenu({
|
||||
endpoint: undefined,
|
||||
endpointType: undefined,
|
||||
});
|
||||
|
||||
const button = screen.getByRole('button', { name: /attach file options/i });
|
||||
expect(button).toBeInTheDocument();
|
||||
fireEvent.click(button);
|
||||
|
||||
// Should show Upload Image as fallback
|
||||
expect(screen.getByText('Upload Image')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should handle null endpoint and provider gracefully', () => {
|
||||
mockUseAgentToolPermissions.mockReturnValue({
|
||||
fileSearchAllowedByAgent: false,
|
||||
codeAllowedByAgent: false,
|
||||
provider: null,
|
||||
});
|
||||
|
||||
renderAttachFileMenu({
|
||||
endpoint: null,
|
||||
endpointType: null,
|
||||
});
|
||||
|
||||
const button = screen.getByRole('button', { name: /attach file options/i });
|
||||
expect(button).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should handle missing agentId gracefully', () => {
|
||||
renderAttachFileMenu({
|
||||
agentId: undefined,
|
||||
endpointType: EModelEndpoint.openAI,
|
||||
});
|
||||
|
||||
const button = screen.getByRole('button', { name: /attach file options/i });
|
||||
expect(button).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should handle empty string agentId', () => {
|
||||
renderAttachFileMenu({
|
||||
agentId: '',
|
||||
endpointType: EModelEndpoint.openAI,
|
||||
});
|
||||
|
||||
const button = screen.getByRole('button', { name: /attach file options/i });
|
||||
expect(button).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Google Provider Special Case', () => {
|
||||
it('should use google_multimodal file type for Google provider', () => {
|
||||
mockUseAgentToolPermissions.mockReturnValue({
|
||||
fileSearchAllowedByAgent: false,
|
||||
codeAllowedByAgent: false,
|
||||
provider: EModelEndpoint.google,
|
||||
});
|
||||
|
||||
renderAttachFileMenu({
|
||||
endpoint: EModelEndpoint.google,
|
||||
endpointType: EModelEndpoint.google,
|
||||
});
|
||||
|
||||
const button = screen.getByRole('button', { name: /attach file options/i });
|
||||
fireEvent.click(button);
|
||||
|
||||
const uploadProviderButton = screen.getByText('Upload to Provider');
|
||||
expect(uploadProviderButton).toBeInTheDocument();
|
||||
|
||||
// Click the upload to provider option
|
||||
fireEvent.click(uploadProviderButton);
|
||||
|
||||
// The file input should have been clicked (indirectly tested through the implementation)
|
||||
});
|
||||
|
||||
it('should use multimodal file type for non-Google providers', () => {
|
||||
mockUseAgentToolPermissions.mockReturnValue({
|
||||
fileSearchAllowedByAgent: false,
|
||||
codeAllowedByAgent: false,
|
||||
provider: EModelEndpoint.openAI,
|
||||
});
|
||||
|
||||
renderAttachFileMenu({
|
||||
endpoint: EModelEndpoint.openAI,
|
||||
endpointType: EModelEndpoint.openAI,
|
||||
});
|
||||
|
||||
const button = screen.getByRole('button', { name: /attach file options/i });
|
||||
fireEvent.click(button);
|
||||
|
||||
const uploadProviderButton = screen.getByText('Upload to Provider');
|
||||
expect(uploadProviderButton).toBeInTheDocument();
|
||||
fireEvent.click(uploadProviderButton);
|
||||
|
||||
// Implementation detail - multimodal type is used
|
||||
});
|
||||
});
|
||||
|
||||
describe('Regression Tests', () => {
|
||||
it('should not break the previous behavior for direct provider attachments', () => {
|
||||
// When using a direct supported provider (not through a gateway)
|
||||
mockUseAgentToolPermissions.mockReturnValue({
|
||||
fileSearchAllowedByAgent: false,
|
||||
codeAllowedByAgent: false,
|
||||
provider: EModelEndpoint.anthropic,
|
||||
});
|
||||
|
||||
renderAttachFileMenu({
|
||||
endpoint: EModelEndpoint.anthropic,
|
||||
endpointType: EModelEndpoint.anthropic,
|
||||
});
|
||||
|
||||
const button = screen.getByRole('button', { name: /attach file options/i });
|
||||
fireEvent.click(button);
|
||||
|
||||
expect(screen.getByText('Upload to Provider')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should maintain correct priority when both are supported', () => {
|
||||
// Both endpointType and provider are supported, endpointType should be checked first
|
||||
mockUseAgentToolPermissions.mockReturnValue({
|
||||
fileSearchAllowedByAgent: false,
|
||||
codeAllowedByAgent: false,
|
||||
provider: EModelEndpoint.google,
|
||||
});
|
||||
|
||||
renderAttachFileMenu({
|
||||
endpoint: EModelEndpoint.google,
|
||||
endpointType: EModelEndpoint.openAI, // Different but both supported
|
||||
});
|
||||
|
||||
const button = screen.getByRole('button', { name: /attach file options/i });
|
||||
fireEvent.click(button);
|
||||
|
||||
// Should still work because endpointType (openAI) is supported
|
||||
expect(screen.getByText('Upload to Provider')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
import { EModelEndpoint, isDocumentSupportedProvider } from 'librechat-data-provider';
|
||||
|
||||
describe('DragDropModal - Provider Detection', () => {
|
||||
describe('endpointType priority over currentProvider', () => {
|
||||
it('should show upload option for LiteLLM with OpenAI endpointType', () => {
|
||||
const currentProvider = 'litellm'; // NOT in documentSupportedProviders
|
||||
const endpointType = EModelEndpoint.openAI; // IS in documentSupportedProviders
|
||||
|
||||
// With fix: endpointType checked
|
||||
const withFix =
|
||||
isDocumentSupportedProvider(endpointType) || isDocumentSupportedProvider(currentProvider);
|
||||
expect(withFix).toBe(true);
|
||||
|
||||
// Without fix: only currentProvider checked = false
|
||||
const withoutFix = isDocumentSupportedProvider(currentProvider || endpointType);
|
||||
expect(withoutFix).toBe(false);
|
||||
});
|
||||
|
||||
it('should show upload option for any custom gateway with OpenAI endpointType', () => {
|
||||
const currentProvider = 'my-custom-gateway';
|
||||
const endpointType = EModelEndpoint.openAI;
|
||||
|
||||
const result =
|
||||
isDocumentSupportedProvider(endpointType) || isDocumentSupportedProvider(currentProvider);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should fallback to currentProvider when endpointType is undefined', () => {
|
||||
const currentProvider = EModelEndpoint.openAI;
|
||||
const endpointType = undefined;
|
||||
|
||||
const result =
|
||||
isDocumentSupportedProvider(endpointType) || isDocumentSupportedProvider(currentProvider);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should fallback to currentProvider when endpointType is null', () => {
|
||||
const currentProvider = EModelEndpoint.anthropic;
|
||||
const endpointType = null;
|
||||
|
||||
const result =
|
||||
isDocumentSupportedProvider(endpointType as any) ||
|
||||
isDocumentSupportedProvider(currentProvider);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when neither provider supports documents', () => {
|
||||
const currentProvider = 'unsupported-provider';
|
||||
const endpointType = 'unsupported-endpoint' as any;
|
||||
|
||||
const result =
|
||||
isDocumentSupportedProvider(endpointType) || isDocumentSupportedProvider(currentProvider);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('supported providers', () => {
|
||||
const supportedProviders = [
|
||||
{ name: 'OpenAI', value: EModelEndpoint.openAI },
|
||||
{ name: 'Anthropic', value: EModelEndpoint.anthropic },
|
||||
{ name: 'Google', value: EModelEndpoint.google },
|
||||
{ name: 'Azure OpenAI', value: EModelEndpoint.azureOpenAI },
|
||||
{ name: 'Custom', value: EModelEndpoint.custom },
|
||||
];
|
||||
|
||||
supportedProviders.forEach(({ name, value }) => {
|
||||
it(`should recognize ${name} as supported`, () => {
|
||||
expect(isDocumentSupportedProvider(value)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('real-world scenarios', () => {
|
||||
it('should handle LiteLLM gateway pointing to OpenAI', () => {
|
||||
const scenario = {
|
||||
currentProvider: 'litellm',
|
||||
endpointType: EModelEndpoint.openAI,
|
||||
};
|
||||
|
||||
expect(
|
||||
isDocumentSupportedProvider(scenario.endpointType) ||
|
||||
isDocumentSupportedProvider(scenario.currentProvider),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should handle direct OpenAI connection', () => {
|
||||
const scenario = {
|
||||
currentProvider: EModelEndpoint.openAI,
|
||||
endpointType: EModelEndpoint.openAI,
|
||||
};
|
||||
|
||||
expect(
|
||||
isDocumentSupportedProvider(scenario.endpointType) ||
|
||||
isDocumentSupportedProvider(scenario.currentProvider),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should handle unsupported custom endpoint without override', () => {
|
||||
const scenario = {
|
||||
currentProvider: 'my-unsupported-endpoint',
|
||||
endpointType: undefined,
|
||||
};
|
||||
|
||||
expect(
|
||||
isDocumentSupportedProvider(scenario.endpointType) ||
|
||||
isDocumentSupportedProvider(scenario.currentProvider),
|
||||
).toBe(false);
|
||||
});
|
||||
it('should handle agents endpoints with document supported providers', () => {
|
||||
const scenario = {
|
||||
currentProvider: EModelEndpoint.google,
|
||||
endpointType: EModelEndpoint.agents,
|
||||
};
|
||||
|
||||
expect(
|
||||
isDocumentSupportedProvider(scenario.endpointType) ||
|
||||
isDocumentSupportedProvider(scenario.currentProvider),
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue