mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-03-09 17:42:38 +01:00
⛵ fix: Resolve Agent Provider Endpoint Type for File Upload Support (#12117)
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile, librechat-dev, node) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile.multi, librechat-dev-api, api-build) (push) Waiting to run
Sync Locize Translations & Create Translation PR / Sync Translation Keys with Locize (push) Waiting to run
Sync Locize Translations & Create Translation PR / Create Translation PR on Version Published (push) Blocked by required conditions
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile, librechat-dev, node) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile.multi, librechat-dev-api, api-build) (push) Waiting to run
Sync Locize Translations & Create Translation PR / Sync Translation Keys with Locize (push) Waiting to run
Sync Locize Translations & Create Translation PR / Create Translation PR on Version Published (push) Blocked by required conditions
* chore: Remove unused setValueOnChange prop from MCPServerMenuItem component
* fix: Resolve agent provider endpoint type for file upload support
When using the agents endpoint with a custom provider (e.g., Moonshot),
the endpointType was resolving to "agents" instead of the provider's
actual type ("custom"), causing "Upload to Provider" to not appear in
the file attach menu.
Adds `resolveEndpointType` utility in data-provider that follows the
chain: endpoint (if not agents) → agent.provider → agents. Applied
consistently across AttachFileChat, DragDropContext, useDragHelpers,
and AgentPanel file components (FileContext, FileSearch, Code/Files).
* refactor: Extract useAgentFileConfig hook, restore deleted tests, fix review findings
- Extract shared provider resolution logic into useAgentFileConfig hook
(Finding #2: DRY violation across FileContext, FileSearch, Code/Files)
- Restore 18 deleted test cases in AttachFileMenu.spec.tsx covering
agent capabilities, SharePoint, edge cases, and button state
(Finding #1: accidental test deletion)
- Wrap fileConfigEndpoint in useMemo in AttachFileChat (Finding #3)
- Fix misleading test name in AgentFileConfig.spec.tsx (Finding #4)
- Fix import order in FileSearch.tsx, FileContext.tsx, Code/Files.tsx (Finding #5)
- Add comment about cache gap in useDragHelpers (Finding #6)
- Clarify resolveEndpointType JSDoc (Finding #7)
* refactor: Memoize Footer component for performance optimization
- Converted Footer component to a memoized version to prevent unnecessary re-renders.
- Improved import structure by adding memo to the React import statement for clarity.
* chore: Fix remaining review nits
- Widen useAgentFileConfig return type to EModelEndpoint | string
- Fix import order in FileContext.tsx and FileSearch.tsx
- Remove dead endpointType param from setupMocks in AttachFileMenu test
* fix: Pass resolved provider endpoint to file upload validation
AgentPanel file components (FileContext, FileSearch, Code/Files) were
hardcoding endpointOverride to "agents", causing both client-side
validation (file limits, MIME types) and server-side validation to
use the agents config instead of the provider-specific config.
Adds endpointTypeOverride to UseFileHandling params so endpoint and
endpointType can be set independently. Components now pass the
resolved provider name and type from useAgentFileConfig, so the full
fallback chain (provider → custom → agents → default) applies to
file upload validation on both client and server.
* test: Verify any custom endpoint is document-supported regardless of name
Adds parameterized tests with arbitrary endpoint names (spaces, hyphens,
colons, etc.) confirming that all custom endpoints resolve to
document-supported through resolveEndpointType, both as direct
endpoints and as agent providers.
* fix: Use || for provider fallback, test endpointOverride wiring
- Change providerValue ?? to providerValue || so empty string is
treated as "no provider" consistently with resolveEndpointType
- Add wiring tests to CodeFiles, FileContext, FileSearch verifying
endpointOverride and endpointTypeOverride are passed correctly
- Update endpointOverride JSDoc to document endpointType fallback
This commit is contained in:
parent
cfaa6337c1
commit
2ac62a2e71
22 changed files with 1573 additions and 582 deletions
|
|
@ -2,10 +2,9 @@ import { memo, useMemo } from 'react';
|
|||
import {
|
||||
Constants,
|
||||
supportsFiles,
|
||||
EModelEndpoint,
|
||||
mergeFileConfig,
|
||||
isAgentsEndpoint,
|
||||
getEndpointField,
|
||||
resolveEndpointType,
|
||||
isAssistantsEndpoint,
|
||||
getEndpointFileConfig,
|
||||
} from 'librechat-data-provider';
|
||||
|
|
@ -55,21 +54,31 @@ function AttachFileChat({
|
|||
|
||||
const { data: endpointsConfig } = useGetEndpointsQuery();
|
||||
|
||||
const endpointType = useMemo(() => {
|
||||
return (
|
||||
getEndpointField(endpointsConfig, endpoint, 'type') ||
|
||||
(endpoint as EModelEndpoint | undefined)
|
||||
);
|
||||
}, [endpoint, endpointsConfig]);
|
||||
const agentProvider = useMemo(() => {
|
||||
if (!isAgents || !conversation?.agent_id) {
|
||||
return undefined;
|
||||
}
|
||||
const agent = agentData || agentsMap?.[conversation.agent_id];
|
||||
return agent?.provider;
|
||||
}, [isAgents, conversation?.agent_id, agentData, agentsMap]);
|
||||
|
||||
const endpointType = useMemo(
|
||||
() => resolveEndpointType(endpointsConfig, endpoint, agentProvider),
|
||||
[endpointsConfig, endpoint, agentProvider],
|
||||
);
|
||||
|
||||
const fileConfigEndpoint = useMemo(
|
||||
() => (isAgents && agentProvider ? agentProvider : endpoint),
|
||||
[isAgents, agentProvider, endpoint],
|
||||
);
|
||||
const endpointFileConfig = useMemo(
|
||||
() =>
|
||||
getEndpointFileConfig({
|
||||
endpoint,
|
||||
fileConfig,
|
||||
endpointType,
|
||||
endpoint: fileConfigEndpoint,
|
||||
}),
|
||||
[endpoint, fileConfig, endpointType],
|
||||
[fileConfigEndpoint, fileConfig, endpointType],
|
||||
);
|
||||
const endpointSupportsFiles: boolean = useMemo(
|
||||
() => supportsFiles[endpointType ?? endpoint ?? ''] ?? false,
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ interface AttachFileMenuProps {
|
|||
endpoint?: string | null;
|
||||
disabled?: boolean | null;
|
||||
conversationId: string;
|
||||
endpointType?: EModelEndpoint;
|
||||
endpointType?: EModelEndpoint | string;
|
||||
endpointFileConfig?: EndpointFileConfig;
|
||||
useResponsesApi?: boolean;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,176 @@
|
|||
import React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { EModelEndpoint, mergeFileConfig } from 'librechat-data-provider';
|
||||
import type { TEndpointsConfig, Agent } from 'librechat-data-provider';
|
||||
import AttachFileChat from '../AttachFileChat';
|
||||
|
||||
const mockEndpointsConfig: TEndpointsConfig = {
|
||||
[EModelEndpoint.openAI]: { userProvide: false, order: 0 },
|
||||
[EModelEndpoint.agents]: { userProvide: false, order: 1 },
|
||||
[EModelEndpoint.assistants]: { userProvide: false, order: 2 },
|
||||
Moonshot: { type: EModelEndpoint.custom, userProvide: false, order: 9999 },
|
||||
};
|
||||
|
||||
const mockFileConfig = mergeFileConfig({
|
||||
endpoints: {
|
||||
Moonshot: { fileLimit: 5 },
|
||||
[EModelEndpoint.agents]: { fileLimit: 20 },
|
||||
default: { fileLimit: 10 },
|
||||
},
|
||||
});
|
||||
|
||||
let mockAgentsMap: Record<string, Partial<Agent>> = {};
|
||||
let mockAgentQueryData: Partial<Agent> | undefined;
|
||||
|
||||
jest.mock('~/data-provider', () => ({
|
||||
useGetEndpointsQuery: () => ({ data: mockEndpointsConfig }),
|
||||
useGetFileConfig: ({ select }: { select?: (data: unknown) => unknown }) => ({
|
||||
data: select != null ? select(mockFileConfig) : mockFileConfig,
|
||||
}),
|
||||
useGetAgentByIdQuery: () => ({ data: mockAgentQueryData }),
|
||||
}));
|
||||
|
||||
jest.mock('~/Providers', () => ({
|
||||
useAgentsMapContext: () => mockAgentsMap,
|
||||
}));
|
||||
|
||||
/** Capture the props passed to AttachFileMenu */
|
||||
let mockAttachFileMenuProps: Record<string, unknown> = {};
|
||||
jest.mock('../AttachFileMenu', () => {
|
||||
return function MockAttachFileMenu(props: Record<string, unknown>) {
|
||||
mockAttachFileMenuProps = props;
|
||||
return <div data-testid="attach-file-menu" data-endpoint-type={String(props.endpointType)} />;
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('../AttachFile', () => {
|
||||
return function MockAttachFile() {
|
||||
return <div data-testid="attach-file" />;
|
||||
};
|
||||
});
|
||||
|
||||
const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
||||
|
||||
function renderComponent(conversation: Record<string, unknown> | null, disableInputs = false) {
|
||||
return render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<RecoilRoot>
|
||||
<AttachFileChat conversation={conversation as never} disableInputs={disableInputs} />
|
||||
</RecoilRoot>
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
}
|
||||
|
||||
describe('AttachFileChat', () => {
|
||||
beforeEach(() => {
|
||||
mockAgentsMap = {};
|
||||
mockAgentQueryData = undefined;
|
||||
mockAttachFileMenuProps = {};
|
||||
});
|
||||
|
||||
describe('rendering decisions', () => {
|
||||
it('renders AttachFileMenu for agents endpoint', () => {
|
||||
renderComponent({ endpoint: EModelEndpoint.agents, agent_id: 'agent-1' });
|
||||
expect(screen.getByTestId('attach-file-menu')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders AttachFileMenu for custom endpoint with file support', () => {
|
||||
renderComponent({ endpoint: 'Moonshot' });
|
||||
expect(screen.getByTestId('attach-file-menu')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders null for null conversation', () => {
|
||||
const { container } = renderComponent(null);
|
||||
expect(container.innerHTML).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('endpointType resolution for agents', () => {
|
||||
it('passes custom endpointType when agent provider is a custom endpoint', () => {
|
||||
mockAgentsMap = {
|
||||
'agent-1': { provider: 'Moonshot', model_parameters: {} } as Partial<Agent>,
|
||||
};
|
||||
renderComponent({ endpoint: EModelEndpoint.agents, agent_id: 'agent-1' });
|
||||
expect(mockAttachFileMenuProps.endpointType).toBe(EModelEndpoint.custom);
|
||||
});
|
||||
|
||||
it('passes openAI endpointType when agent provider is openAI', () => {
|
||||
mockAgentsMap = {
|
||||
'agent-1': { provider: EModelEndpoint.openAI, model_parameters: {} } as Partial<Agent>,
|
||||
};
|
||||
renderComponent({ endpoint: EModelEndpoint.agents, agent_id: 'agent-1' });
|
||||
expect(mockAttachFileMenuProps.endpointType).toBe(EModelEndpoint.openAI);
|
||||
});
|
||||
|
||||
it('passes agents endpointType when no agent provider', () => {
|
||||
renderComponent({ endpoint: EModelEndpoint.agents, agent_id: 'agent-1' });
|
||||
expect(mockAttachFileMenuProps.endpointType).toBe(EModelEndpoint.agents);
|
||||
});
|
||||
|
||||
it('passes agents endpointType when no agent_id', () => {
|
||||
renderComponent({ endpoint: EModelEndpoint.agents });
|
||||
expect(mockAttachFileMenuProps.endpointType).toBe(EModelEndpoint.agents);
|
||||
});
|
||||
|
||||
it('uses agentData query when agent not in agentsMap', () => {
|
||||
mockAgentQueryData = { provider: 'Moonshot' } as Partial<Agent>;
|
||||
renderComponent({ endpoint: EModelEndpoint.agents, agent_id: 'agent-2' });
|
||||
expect(mockAttachFileMenuProps.endpointType).toBe(EModelEndpoint.custom);
|
||||
});
|
||||
});
|
||||
|
||||
describe('endpointType resolution for non-agents', () => {
|
||||
it('passes custom endpointType for a custom endpoint', () => {
|
||||
renderComponent({ endpoint: 'Moonshot' });
|
||||
expect(mockAttachFileMenuProps.endpointType).toBe(EModelEndpoint.custom);
|
||||
});
|
||||
|
||||
it('passes openAI endpointType for openAI endpoint', () => {
|
||||
renderComponent({ endpoint: EModelEndpoint.openAI });
|
||||
expect(mockAttachFileMenuProps.endpointType).toBe(EModelEndpoint.openAI);
|
||||
});
|
||||
});
|
||||
|
||||
describe('consistency: same endpoint type for direct vs agent usage', () => {
|
||||
it('resolves Moonshot the same way whether used directly or through an agent', () => {
|
||||
renderComponent({ endpoint: 'Moonshot' });
|
||||
const directType = mockAttachFileMenuProps.endpointType;
|
||||
|
||||
mockAgentsMap = {
|
||||
'agent-1': { provider: 'Moonshot', model_parameters: {} } as Partial<Agent>,
|
||||
};
|
||||
renderComponent({ endpoint: EModelEndpoint.agents, agent_id: 'agent-1' });
|
||||
const agentType = mockAttachFileMenuProps.endpointType;
|
||||
|
||||
expect(directType).toBe(agentType);
|
||||
});
|
||||
});
|
||||
|
||||
describe('endpointFileConfig resolution', () => {
|
||||
it('passes Moonshot-specific file config for agent with Moonshot provider', () => {
|
||||
mockAgentsMap = {
|
||||
'agent-1': { provider: 'Moonshot', model_parameters: {} } as Partial<Agent>,
|
||||
};
|
||||
renderComponent({ endpoint: EModelEndpoint.agents, agent_id: 'agent-1' });
|
||||
const config = mockAttachFileMenuProps.endpointFileConfig as { fileLimit?: number };
|
||||
expect(config?.fileLimit).toBe(5);
|
||||
});
|
||||
|
||||
it('passes agents file config when agent has no specific provider config', () => {
|
||||
mockAgentsMap = {
|
||||
'agent-1': { provider: EModelEndpoint.openAI, model_parameters: {} } as Partial<Agent>,
|
||||
};
|
||||
renderComponent({ endpoint: EModelEndpoint.agents, agent_id: 'agent-1' });
|
||||
const config = mockAttachFileMenuProps.endpointFileConfig as { fileLimit?: number };
|
||||
expect(config?.fileLimit).toBe(10);
|
||||
});
|
||||
|
||||
it('passes agents file config when no agent provider', () => {
|
||||
renderComponent({ endpoint: EModelEndpoint.agents });
|
||||
const config = mockAttachFileMenuProps.endpointFileConfig as { fileLimit?: number };
|
||||
expect(config?.fileLimit).toBe(20);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,12 +1,10 @@
|
|||
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 { EModelEndpoint, Providers } from 'librechat-data-provider';
|
||||
import AttachFileMenu from '../AttachFileMenu';
|
||||
|
||||
// Mock all the hooks
|
||||
jest.mock('~/hooks', () => ({
|
||||
useAgentToolPermissions: jest.fn(),
|
||||
useAgentCapabilities: jest.fn(),
|
||||
|
|
@ -25,53 +23,45 @@ jest.mock('~/data-provider', () => ({
|
|||
}));
|
||||
|
||||
jest.mock('~/components/SharePoint', () => ({
|
||||
SharePointPickerDialog: jest.fn(() => null),
|
||||
SharePointPickerDialog: () => null,
|
||||
}));
|
||||
|
||||
jest.mock('@librechat/client', () => {
|
||||
const React = jest.requireActual('react');
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
const R = require('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>,
|
||||
FileUpload: (props) => R.createElement('div', { 'data-testid': 'file-upload' }, props.children),
|
||||
TooltipAnchor: (props) => props.render,
|
||||
DropdownPopup: (props) =>
|
||||
R.createElement(
|
||||
'div',
|
||||
null,
|
||||
R.createElement('div', { onClick: () => props.setIsOpen(!props.isOpen) }, props.trigger),
|
||||
props.isOpen &&
|
||||
R.createElement(
|
||||
'div',
|
||||
{ 'data-testid': 'dropdown-menu' },
|
||||
props.items.map((item, idx) =>
|
||||
R.createElement(
|
||||
'button',
|
||||
{ key: idx, onClick: item.onClick, 'data-testid': `menu-item-${idx}` },
|
||||
item.label,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
AttachmentIcon: () => R.createElement('span', { 'data-testid': 'attachment-icon' }),
|
||||
SharePointIcon: () => R.createElement('span', { 'data-testid': 'sharepoint-icon' }),
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('@ariakit/react', () => ({
|
||||
MenuButton: ({ children, onClick, disabled, ...props }: any) => (
|
||||
<button onClick={onClick} disabled={disabled} {...props}>
|
||||
{children}
|
||||
</button>
|
||||
),
|
||||
}));
|
||||
jest.mock('@ariakit/react', () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
const R = require('react');
|
||||
return {
|
||||
MenuButton: (props) => R.createElement('button', props, props.children),
|
||||
};
|
||||
});
|
||||
|
||||
const mockUseAgentToolPermissions = jest.requireMock('~/hooks').useAgentToolPermissions;
|
||||
const mockUseAgentCapabilities = jest.requireMock('~/hooks').useAgentCapabilities;
|
||||
|
|
@ -83,558 +73,283 @@ const mockUseSharePointFileHandling = jest.requireMock(
|
|||
).default;
|
||||
const mockUseGetStartupConfig = jest.requireMock('~/data-provider').useGetStartupConfig;
|
||||
|
||||
describe('AttachFileMenu', () => {
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: { retry: false },
|
||||
},
|
||||
});
|
||||
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>,
|
||||
);
|
||||
function setupMocks(overrides: { provider?: 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 as 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',
|
||||
};
|
||||
|
||||
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();
|
||||
});
|
||||
mockUseLocalize.mockReturnValue((key: string) => translations[key] || key);
|
||||
mockUseAgentCapabilities.mockReturnValue({
|
||||
contextEnabled: false,
|
||||
fileSearchEnabled: false,
|
||||
codeEnabled: false,
|
||||
});
|
||||
mockUseGetAgentsConfig.mockReturnValue({ agentsConfig: {} });
|
||||
mockUseFileHandling.mockReturnValue({ handleFileChange: jest.fn() });
|
||||
mockUseSharePointFileHandling.mockReturnValue({
|
||||
handleSharePointFiles: jest.fn(),
|
||||
isProcessing: false,
|
||||
downloadProgress: 0,
|
||||
});
|
||||
mockUseGetStartupConfig.mockReturnValue({ data: { sharePointFilePickerEnabled: false } });
|
||||
mockUseAgentToolPermissions.mockReturnValue({
|
||||
fileSearchAllowedByAgent: false,
|
||||
codeAllowedByAgent: false,
|
||||
provider: overrides.provider ?? undefined,
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
});
|
||||
function renderMenu(props: Record<string, unknown> = {}) {
|
||||
return render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<RecoilRoot>
|
||||
<AttachFileMenu conversationId="test-convo" {...props} />
|
||||
</RecoilRoot>
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
}
|
||||
|
||||
renderAttachFileMenu({
|
||||
endpoint: 'litellm',
|
||||
endpointType: EModelEndpoint.openAI, // Backend override IS in documentSupportedProviders
|
||||
});
|
||||
function openMenu() {
|
||||
fireEvent.click(screen.getByRole('button', { name: /attach file options/i }));
|
||||
}
|
||||
|
||||
const button = screen.getByRole('button', { name: /attach file options/i });
|
||||
fireEvent.click(button);
|
||||
describe('AttachFileMenu', () => {
|
||||
beforeEach(jest.clearAllMocks);
|
||||
|
||||
// With the fix, should show "Upload to Provider" because endpointType is checked first
|
||||
describe('Upload to Provider vs Upload Image', () => {
|
||||
it('shows "Upload to Provider" when endpointType is custom (resolved from agent provider)', () => {
|
||||
setupMocks({ provider: 'Moonshot' });
|
||||
renderMenu({ endpointType: EModelEndpoint.custom });
|
||||
openMenu();
|
||||
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);
|
||||
|
||||
it('shows "Upload to Provider" when endpointType is openAI', () => {
|
||||
setupMocks({ provider: EModelEndpoint.openAI });
|
||||
renderMenu({ endpointType: EModelEndpoint.openAI });
|
||||
openMenu();
|
||||
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',
|
||||
});
|
||||
it('shows "Upload to Provider" when endpointType is anthropic', () => {
|
||||
setupMocks({ provider: EModelEndpoint.anthropic });
|
||||
renderMenu({ endpointType: EModelEndpoint.anthropic });
|
||||
openMenu();
|
||||
expect(screen.getByText('Upload to Provider')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
renderAttachFileMenu({
|
||||
endpoint: 'unsupported-provider',
|
||||
endpointType: 'unsupported-endpoint' as any,
|
||||
});
|
||||
|
||||
const button = screen.getByRole('button', { name: /attach file options/i });
|
||||
fireEvent.click(button);
|
||||
it('shows "Upload to Provider" when endpointType is google', () => {
|
||||
setupMocks({ provider: Providers.GOOGLE });
|
||||
renderMenu({ endpointType: EModelEndpoint.google });
|
||||
openMenu();
|
||||
expect(screen.getByText('Upload to Provider')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows "Upload Image" when endpointType is agents (no provider resolution)', () => {
|
||||
setupMocks();
|
||||
renderMenu({ endpointType: EModelEndpoint.agents });
|
||||
openMenu();
|
||||
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);
|
||||
it('shows "Upload Image" when neither endpointType nor provider supports documents', () => {
|
||||
setupMocks({ provider: 'unknown-provider' });
|
||||
renderMenu({ endpointType: 'unknown-type' });
|
||||
openMenu();
|
||||
expect(screen.getByText('Upload Image')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows "Upload to Provider" for azureOpenAI with useResponsesApi', () => {
|
||||
setupMocks({ provider: EModelEndpoint.azureOpenAI });
|
||||
renderMenu({ endpointType: EModelEndpoint.azureOpenAI, useResponsesApi: true });
|
||||
openMenu();
|
||||
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();
|
||||
it('shows "Upload Image" for azureOpenAI without useResponsesApi', () => {
|
||||
setupMocks({ provider: EModelEndpoint.azureOpenAI });
|
||||
renderMenu({ endpointType: EModelEndpoint.azureOpenAI, useResponsesApi: false });
|
||||
openMenu();
|
||||
expect(screen.getByText('Upload Image')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Supported Providers', () => {
|
||||
const supportedProviders = [
|
||||
{ name: 'OpenAI', endpoint: EModelEndpoint.openAI },
|
||||
{ name: 'Anthropic', endpoint: EModelEndpoint.anthropic },
|
||||
{ name: 'Google', endpoint: EModelEndpoint.google },
|
||||
{ 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 provider resolution scenario', () => {
|
||||
it('shows "Upload to Provider" when agents endpoint has custom endpointType from provider', () => {
|
||||
setupMocks({ provider: 'Moonshot' });
|
||||
renderMenu({
|
||||
endpoint: EModelEndpoint.agents,
|
||||
endpointType: EModelEndpoint.custom,
|
||||
});
|
||||
});
|
||||
|
||||
it('should show Upload to Provider for Azure OpenAI with useResponsesApi', () => {
|
||||
mockUseAgentToolPermissions.mockReturnValue({
|
||||
fileSearchAllowedByAgent: false,
|
||||
codeAllowedByAgent: false,
|
||||
provider: EModelEndpoint.azureOpenAI,
|
||||
});
|
||||
|
||||
renderAttachFileMenu({
|
||||
endpoint: EModelEndpoint.azureOpenAI,
|
||||
endpointType: EModelEndpoint.azureOpenAI,
|
||||
useResponsesApi: true,
|
||||
});
|
||||
|
||||
const button = screen.getByRole('button', { name: /attach file options/i });
|
||||
fireEvent.click(button);
|
||||
|
||||
openMenu();
|
||||
expect(screen.getByText('Upload to Provider')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should NOT show Upload to Provider for Azure OpenAI without useResponsesApi', () => {
|
||||
mockUseAgentToolPermissions.mockReturnValue({
|
||||
fileSearchAllowedByAgent: false,
|
||||
codeAllowedByAgent: false,
|
||||
provider: EModelEndpoint.azureOpenAI,
|
||||
it('shows "Upload Image" when agents endpoint has no resolved provider type', () => {
|
||||
setupMocks();
|
||||
renderMenu({
|
||||
endpoint: EModelEndpoint.agents,
|
||||
endpointType: EModelEndpoint.agents,
|
||||
});
|
||||
|
||||
renderAttachFileMenu({
|
||||
endpoint: EModelEndpoint.azureOpenAI,
|
||||
endpointType: EModelEndpoint.azureOpenAI,
|
||||
useResponsesApi: false,
|
||||
});
|
||||
|
||||
const button = screen.getByRole('button', { name: /attach file options/i });
|
||||
fireEvent.click(button);
|
||||
|
||||
expect(screen.queryByText('Upload to Provider')).not.toBeInTheDocument();
|
||||
openMenu();
|
||||
expect(screen.getByText('Upload Image')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Basic Rendering', () => {
|
||||
it('renders the attachment button', () => {
|
||||
setupMocks();
|
||||
renderMenu();
|
||||
expect(screen.getByRole('button', { name: /attach file options/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('is disabled when disabled prop is true', () => {
|
||||
setupMocks();
|
||||
renderMenu({ disabled: true });
|
||||
expect(screen.getByRole('button', { name: /attach file options/i })).toBeDisabled();
|
||||
});
|
||||
|
||||
it('is not disabled when disabled prop is false', () => {
|
||||
setupMocks();
|
||||
renderMenu({ disabled: false });
|
||||
expect(screen.getByRole('button', { name: /attach file options/i })).not.toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Agent Capabilities', () => {
|
||||
it('should show OCR Text option when context is enabled', () => {
|
||||
it('shows OCR Text option when context is enabled', () => {
|
||||
setupMocks();
|
||||
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();
|
||||
renderMenu({ endpointType: EModelEndpoint.openAI });
|
||||
openMenu();
|
||||
expect(screen.getByText('Upload as Text')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show File Search option when enabled and allowed by agent', () => {
|
||||
it('shows File Search option when enabled and allowed by agent', () => {
|
||||
setupMocks();
|
||||
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);
|
||||
|
||||
renderMenu({ endpointType: EModelEndpoint.openAI });
|
||||
openMenu();
|
||||
expect(screen.getByText('Upload for File Search')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should NOT show File Search when enabled but not allowed by agent', () => {
|
||||
it('does NOT show File Search when enabled but not allowed by agent', () => {
|
||||
setupMocks();
|
||||
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);
|
||||
|
||||
renderMenu({ endpointType: EModelEndpoint.openAI });
|
||||
openMenu();
|
||||
expect(screen.queryByText('Upload for File Search')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show Code Files option when enabled and allowed by agent', () => {
|
||||
it('shows Code Files option when enabled and allowed by agent', () => {
|
||||
setupMocks();
|
||||
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);
|
||||
|
||||
renderMenu({ endpointType: EModelEndpoint.openAI });
|
||||
openMenu();
|
||||
expect(screen.getByText('Upload Code Files')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show all options when all capabilities are enabled', () => {
|
||||
it('shows all options when all capabilities are enabled', () => {
|
||||
setupMocks();
|
||||
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);
|
||||
|
||||
renderMenu({ endpointType: EModelEndpoint.openAI });
|
||||
openMenu();
|
||||
expect(screen.getByText('Upload to Provider')).toBeInTheDocument();
|
||||
expect(screen.getByText('Upload OCR Text')).toBeInTheDocument();
|
||||
expect(screen.getByText('Upload as 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', () => {
|
||||
it('shows SharePoint option when enabled', () => {
|
||||
setupMocks();
|
||||
mockUseGetStartupConfig.mockReturnValue({
|
||||
data: {
|
||||
sharePointFilePickerEnabled: true,
|
||||
},
|
||||
data: { sharePointFilePickerEnabled: true },
|
||||
});
|
||||
|
||||
renderAttachFileMenu({
|
||||
endpointType: EModelEndpoint.openAI,
|
||||
});
|
||||
|
||||
const button = screen.getByRole('button', { name: /attach file options/i });
|
||||
fireEvent.click(button);
|
||||
|
||||
renderMenu({ endpointType: EModelEndpoint.openAI });
|
||||
openMenu();
|
||||
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);
|
||||
|
||||
it('does NOT show SharePoint option when disabled', () => {
|
||||
setupMocks();
|
||||
renderMenu({ endpointType: EModelEndpoint.openAI });
|
||||
openMenu();
|
||||
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,
|
||||
});
|
||||
|
||||
it('handles undefined endpoint and provider gracefully', () => {
|
||||
setupMocks();
|
||||
renderMenu({ 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('handles null endpoint and provider gracefully', () => {
|
||||
setupMocks();
|
||||
renderMenu({ endpoint: null, endpointType: null });
|
||||
expect(screen.getByRole('button', { name: /attach file options/i })).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('handles missing agentId gracefully', () => {
|
||||
setupMocks();
|
||||
renderMenu({ agentId: undefined, endpointType: EModelEndpoint.openAI });
|
||||
expect(screen.getByRole('button', { name: /attach file options/i })).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 image_document_video_audio 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 image_document 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 - image_document 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();
|
||||
it('handles empty string agentId', () => {
|
||||
setupMocks();
|
||||
renderMenu({ agentId: '', endpointType: EModelEndpoint.openAI });
|
||||
expect(screen.getByRole('button', { name: /attach file options/i })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue