mirror of
https://github.com/langgenius/dify.git
synced 2026-05-05 18:08:07 +08:00
test: add tests for dataset document detail (#31274)
Co-authored-by: CodingOnStar <hanxujiang@dify.ai> Co-authored-by: CodingOnStar <hanxujiang@dify.com>
This commit is contained in:
@ -1,5 +1,6 @@
|
||||
import type { Credential } from '@/app/components/tools/types'
|
||||
import { act, fireEvent, render, screen } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { AuthHeaderPrefix, AuthType } from '@/app/components/tools/types'
|
||||
import ConfigCredential from './config-credentials'
|
||||
|
||||
@ -14,47 +15,472 @@ describe('ConfigCredential', () => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('renders and calls onHide when cancel is pressed', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<ConfigCredential
|
||||
credential={baseCredential}
|
||||
onChange={mockOnChange}
|
||||
onHide={mockOnHide}
|
||||
/>,
|
||||
)
|
||||
// Tests for basic rendering
|
||||
describe('Rendering', () => {
|
||||
it('should render without crashing', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<ConfigCredential
|
||||
credential={baseCredential}
|
||||
onChange={mockOnChange}
|
||||
onHide={mockOnHide}
|
||||
/>,
|
||||
)
|
||||
})
|
||||
|
||||
expect(screen.getByText('tools.createTool.authMethod.title')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
fireEvent.click(screen.getByText('common.operation.cancel'))
|
||||
it('should render all three auth type options', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<ConfigCredential
|
||||
credential={baseCredential}
|
||||
onChange={mockOnChange}
|
||||
onHide={mockOnHide}
|
||||
/>,
|
||||
)
|
||||
})
|
||||
|
||||
expect(mockOnHide).toHaveBeenCalledTimes(1)
|
||||
expect(mockOnChange).not.toHaveBeenCalled()
|
||||
expect(screen.getByText('tools.createTool.authMethod.types.none')).toBeInTheDocument()
|
||||
expect(screen.getByText('tools.createTool.authMethod.types.api_key_header')).toBeInTheDocument()
|
||||
expect(screen.getByText('tools.createTool.authMethod.types.api_key_query')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render with positionCenter prop', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<ConfigCredential
|
||||
positionCenter
|
||||
credential={baseCredential}
|
||||
onChange={mockOnChange}
|
||||
onHide={mockOnHide}
|
||||
/>,
|
||||
)
|
||||
})
|
||||
|
||||
expect(screen.getByText('tools.createTool.authMethod.title')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('allows selecting apiKeyHeader and submits the new credential', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<ConfigCredential
|
||||
credential={baseCredential}
|
||||
onChange={mockOnChange}
|
||||
onHide={mockOnHide}
|
||||
/>,
|
||||
// Tests for cancel and save buttons
|
||||
describe('Cancel and Save Actions', () => {
|
||||
it('should call onHide when cancel is pressed', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<ConfigCredential
|
||||
credential={baseCredential}
|
||||
onChange={mockOnChange}
|
||||
onHide={mockOnHide}
|
||||
/>,
|
||||
)
|
||||
})
|
||||
|
||||
fireEvent.click(screen.getByText('common.operation.cancel'))
|
||||
|
||||
expect(mockOnHide).toHaveBeenCalledTimes(1)
|
||||
expect(mockOnChange).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should call both onChange and onHide when save is pressed', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<ConfigCredential
|
||||
credential={baseCredential}
|
||||
onChange={mockOnChange}
|
||||
onHide={mockOnHide}
|
||||
/>,
|
||||
)
|
||||
})
|
||||
|
||||
fireEvent.click(screen.getByText('common.operation.save'))
|
||||
|
||||
expect(mockOnChange).toHaveBeenCalledTimes(1)
|
||||
expect(mockOnHide).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
// Tests for "none" auth type selection
|
||||
describe('None Auth Type', () => {
|
||||
it('should select none auth type and save', async () => {
|
||||
const credentialWithApiKey: Credential = {
|
||||
auth_type: AuthType.apiKeyHeader,
|
||||
api_key_header: 'X-Api-Key',
|
||||
api_key_value: 'test-value',
|
||||
api_key_header_prefix: AuthHeaderPrefix.bearer,
|
||||
}
|
||||
|
||||
await act(async () => {
|
||||
render(
|
||||
<ConfigCredential
|
||||
credential={credentialWithApiKey}
|
||||
onChange={mockOnChange}
|
||||
onHide={mockOnHide}
|
||||
/>,
|
||||
)
|
||||
})
|
||||
|
||||
// Switch to none auth type
|
||||
fireEvent.click(screen.getByText('tools.createTool.authMethod.types.none'))
|
||||
fireEvent.click(screen.getByText('common.operation.save'))
|
||||
|
||||
expect(mockOnChange).toHaveBeenCalledWith({
|
||||
auth_type: AuthType.none,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Tests for API Key Header auth type
|
||||
describe('API Key Header Auth Type', () => {
|
||||
it('should select apiKeyHeader and show header prefix options', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<ConfigCredential
|
||||
credential={baseCredential}
|
||||
onChange={mockOnChange}
|
||||
onHide={mockOnHide}
|
||||
/>,
|
||||
)
|
||||
})
|
||||
|
||||
fireEvent.click(screen.getByText('tools.createTool.authMethod.types.api_key_header'))
|
||||
|
||||
// Header prefix options should appear
|
||||
expect(screen.getByText('tools.createTool.authHeaderPrefix.types.basic')).toBeInTheDocument()
|
||||
expect(screen.getByText('tools.createTool.authHeaderPrefix.types.bearer')).toBeInTheDocument()
|
||||
expect(screen.getByText('tools.createTool.authHeaderPrefix.types.custom')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should submit apiKeyHeader credential with default values', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<ConfigCredential
|
||||
credential={baseCredential}
|
||||
onChange={mockOnChange}
|
||||
onHide={mockOnHide}
|
||||
/>,
|
||||
)
|
||||
})
|
||||
|
||||
fireEvent.click(screen.getByText('tools.createTool.authMethod.types.api_key_header'))
|
||||
const headerInput = screen.getByPlaceholderText('tools.createTool.authMethod.types.apiKeyPlaceholder')
|
||||
const valueInput = screen.getByPlaceholderText('tools.createTool.authMethod.types.apiValuePlaceholder')
|
||||
fireEvent.change(headerInput, { target: { value: 'X-Auth' } })
|
||||
fireEvent.change(valueInput, { target: { value: 'sEcReT' } })
|
||||
fireEvent.click(screen.getByText('common.operation.save'))
|
||||
|
||||
expect(mockOnChange).toHaveBeenCalledWith({
|
||||
auth_type: AuthType.apiKeyHeader,
|
||||
api_key_header: 'X-Auth',
|
||||
api_key_header_prefix: AuthHeaderPrefix.custom,
|
||||
api_key_value: 'sEcReT',
|
||||
})
|
||||
expect(mockOnHide).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should select basic header prefix', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<ConfigCredential
|
||||
credential={baseCredential}
|
||||
onChange={mockOnChange}
|
||||
onHide={mockOnHide}
|
||||
/>,
|
||||
)
|
||||
})
|
||||
|
||||
fireEvent.click(screen.getByText('tools.createTool.authMethod.types.api_key_header'))
|
||||
fireEvent.click(screen.getByText('tools.createTool.authHeaderPrefix.types.basic'))
|
||||
fireEvent.click(screen.getByText('common.operation.save'))
|
||||
|
||||
expect(mockOnChange).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
auth_type: AuthType.apiKeyHeader,
|
||||
api_key_header_prefix: AuthHeaderPrefix.basic,
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
fireEvent.click(screen.getByText('tools.createTool.authMethod.types.api_key_header'))
|
||||
const headerInput = screen.getByPlaceholderText('tools.createTool.authMethod.types.apiKeyPlaceholder')
|
||||
const valueInput = screen.getByPlaceholderText('tools.createTool.authMethod.types.apiValuePlaceholder')
|
||||
fireEvent.change(headerInput, { target: { value: 'X-Auth' } })
|
||||
fireEvent.change(valueInput, { target: { value: 'sEcReT' } })
|
||||
fireEvent.click(screen.getByText('common.operation.save'))
|
||||
it('should select bearer header prefix', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<ConfigCredential
|
||||
credential={baseCredential}
|
||||
onChange={mockOnChange}
|
||||
onHide={mockOnHide}
|
||||
/>,
|
||||
)
|
||||
})
|
||||
|
||||
expect(mockOnChange).toHaveBeenCalledWith({
|
||||
auth_type: AuthType.apiKeyHeader,
|
||||
api_key_header: 'X-Auth',
|
||||
api_key_header_prefix: AuthHeaderPrefix.custom,
|
||||
api_key_value: 'sEcReT',
|
||||
fireEvent.click(screen.getByText('tools.createTool.authMethod.types.api_key_header'))
|
||||
fireEvent.click(screen.getByText('tools.createTool.authHeaderPrefix.types.bearer'))
|
||||
fireEvent.click(screen.getByText('common.operation.save'))
|
||||
|
||||
expect(mockOnChange).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
auth_type: AuthType.apiKeyHeader,
|
||||
api_key_header_prefix: AuthHeaderPrefix.bearer,
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('should select custom header prefix', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<ConfigCredential
|
||||
credential={baseCredential}
|
||||
onChange={mockOnChange}
|
||||
onHide={mockOnHide}
|
||||
/>,
|
||||
)
|
||||
})
|
||||
|
||||
// Start with none, switch to apiKeyHeader (which defaults to custom)
|
||||
fireEvent.click(screen.getByText('tools.createTool.authMethod.types.api_key_header'))
|
||||
// Select bearer first, then custom to test switching
|
||||
fireEvent.click(screen.getByText('tools.createTool.authHeaderPrefix.types.bearer'))
|
||||
fireEvent.click(screen.getByText('tools.createTool.authHeaderPrefix.types.custom'))
|
||||
fireEvent.click(screen.getByText('common.operation.save'))
|
||||
|
||||
expect(mockOnChange).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
auth_type: AuthType.apiKeyHeader,
|
||||
api_key_header_prefix: AuthHeaderPrefix.custom,
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('should preserve existing values when switching to apiKeyHeader', async () => {
|
||||
const existingCredential: Credential = {
|
||||
auth_type: AuthType.none,
|
||||
api_key_header: 'Existing-Header',
|
||||
api_key_value: 'existing-value',
|
||||
api_key_header_prefix: AuthHeaderPrefix.bearer,
|
||||
}
|
||||
|
||||
await act(async () => {
|
||||
render(
|
||||
<ConfigCredential
|
||||
credential={existingCredential}
|
||||
onChange={mockOnChange}
|
||||
onHide={mockOnHide}
|
||||
/>,
|
||||
)
|
||||
})
|
||||
|
||||
fireEvent.click(screen.getByText('tools.createTool.authMethod.types.api_key_header'))
|
||||
fireEvent.click(screen.getByText('common.operation.save'))
|
||||
|
||||
expect(mockOnChange).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
auth_type: AuthType.apiKeyHeader,
|
||||
api_key_header: 'Existing-Header',
|
||||
api_key_value: 'existing-value',
|
||||
api_key_header_prefix: AuthHeaderPrefix.bearer,
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
// Tests for API Key Query auth type
|
||||
describe('API Key Query Auth Type', () => {
|
||||
it('should select apiKeyQuery and show query param input', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<ConfigCredential
|
||||
credential={baseCredential}
|
||||
onChange={mockOnChange}
|
||||
onHide={mockOnHide}
|
||||
/>,
|
||||
)
|
||||
})
|
||||
|
||||
fireEvent.click(screen.getByText('tools.createTool.authMethod.types.api_key_query'))
|
||||
|
||||
// Query param input should appear
|
||||
expect(screen.getByPlaceholderText('tools.createTool.authMethod.types.queryParamPlaceholder')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should submit apiKeyQuery credential with default values', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<ConfigCredential
|
||||
credential={baseCredential}
|
||||
onChange={mockOnChange}
|
||||
onHide={mockOnHide}
|
||||
/>,
|
||||
)
|
||||
})
|
||||
|
||||
fireEvent.click(screen.getByText('tools.createTool.authMethod.types.api_key_query'))
|
||||
fireEvent.click(screen.getByText('common.operation.save'))
|
||||
|
||||
expect(mockOnChange).toHaveBeenCalledWith({
|
||||
auth_type: AuthType.apiKeyQuery,
|
||||
api_key_query_param: 'key',
|
||||
api_key_value: '',
|
||||
})
|
||||
})
|
||||
|
||||
it('should edit query param name and value', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<ConfigCredential
|
||||
credential={baseCredential}
|
||||
onChange={mockOnChange}
|
||||
onHide={mockOnHide}
|
||||
/>,
|
||||
)
|
||||
})
|
||||
|
||||
fireEvent.click(screen.getByText('tools.createTool.authMethod.types.api_key_query'))
|
||||
|
||||
const queryParamInput = screen.getByPlaceholderText('tools.createTool.authMethod.types.queryParamPlaceholder')
|
||||
const valueInput = screen.getByPlaceholderText('tools.createTool.authMethod.types.apiValuePlaceholder')
|
||||
|
||||
fireEvent.change(queryParamInput, { target: { value: 'api_key' } })
|
||||
fireEvent.change(valueInput, { target: { value: 'my-secret-key' } })
|
||||
fireEvent.click(screen.getByText('common.operation.save'))
|
||||
|
||||
expect(mockOnChange).toHaveBeenCalledWith({
|
||||
auth_type: AuthType.apiKeyQuery,
|
||||
api_key_query_param: 'api_key',
|
||||
api_key_value: 'my-secret-key',
|
||||
})
|
||||
})
|
||||
|
||||
it('should preserve existing values when switching to apiKeyQuery', async () => {
|
||||
const existingCredential: Credential = {
|
||||
auth_type: AuthType.none,
|
||||
api_key_query_param: 'existing_param',
|
||||
api_key_value: 'existing-value',
|
||||
}
|
||||
|
||||
await act(async () => {
|
||||
render(
|
||||
<ConfigCredential
|
||||
credential={existingCredential}
|
||||
onChange={mockOnChange}
|
||||
onHide={mockOnHide}
|
||||
/>,
|
||||
)
|
||||
})
|
||||
|
||||
fireEvent.click(screen.getByText('tools.createTool.authMethod.types.api_key_query'))
|
||||
fireEvent.click(screen.getByText('common.operation.save'))
|
||||
|
||||
expect(mockOnChange).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
auth_type: AuthType.apiKeyQuery,
|
||||
api_key_query_param: 'existing_param',
|
||||
api_key_value: 'existing-value',
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
// Tests for switching between auth types
|
||||
describe('Switching Auth Types', () => {
|
||||
it('should switch from apiKeyHeader to apiKeyQuery', async () => {
|
||||
const headerCredential: Credential = {
|
||||
auth_type: AuthType.apiKeyHeader,
|
||||
api_key_header: 'Authorization',
|
||||
api_key_value: 'Bearer token',
|
||||
api_key_header_prefix: AuthHeaderPrefix.bearer,
|
||||
}
|
||||
|
||||
await act(async () => {
|
||||
render(
|
||||
<ConfigCredential
|
||||
credential={headerCredential}
|
||||
onChange={mockOnChange}
|
||||
onHide={mockOnHide}
|
||||
/>,
|
||||
)
|
||||
})
|
||||
|
||||
// Switch to query
|
||||
fireEvent.click(screen.getByText('tools.createTool.authMethod.types.api_key_query'))
|
||||
|
||||
// Header prefix options should disappear
|
||||
expect(screen.queryByText('tools.createTool.authHeaderPrefix.types.basic')).not.toBeInTheDocument()
|
||||
|
||||
// Query param input should appear
|
||||
expect(screen.getByPlaceholderText('tools.createTool.authMethod.types.queryParamPlaceholder')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should switch from apiKeyQuery to none', async () => {
|
||||
const queryCredential: Credential = {
|
||||
auth_type: AuthType.apiKeyQuery,
|
||||
api_key_query_param: 'key',
|
||||
api_key_value: 'value',
|
||||
}
|
||||
|
||||
await act(async () => {
|
||||
render(
|
||||
<ConfigCredential
|
||||
credential={queryCredential}
|
||||
onChange={mockOnChange}
|
||||
onHide={mockOnHide}
|
||||
/>,
|
||||
)
|
||||
})
|
||||
|
||||
// Switch to none
|
||||
fireEvent.click(screen.getByText('tools.createTool.authMethod.types.none'))
|
||||
fireEvent.click(screen.getByText('common.operation.save'))
|
||||
|
||||
expect(mockOnChange).toHaveBeenCalledWith({
|
||||
auth_type: AuthType.none,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Tests for initial credential state
|
||||
describe('Initial Credential State', () => {
|
||||
it('should show apiKeyHeader fields when initial auth type is apiKeyHeader', async () => {
|
||||
const headerCredential: Credential = {
|
||||
auth_type: AuthType.apiKeyHeader,
|
||||
api_key_header: 'X-Custom-Header',
|
||||
api_key_value: 'secret123',
|
||||
api_key_header_prefix: AuthHeaderPrefix.bearer,
|
||||
}
|
||||
|
||||
await act(async () => {
|
||||
render(
|
||||
<ConfigCredential
|
||||
credential={headerCredential}
|
||||
onChange={mockOnChange}
|
||||
onHide={mockOnHide}
|
||||
/>,
|
||||
)
|
||||
})
|
||||
|
||||
// Header inputs should be visible with initial values
|
||||
const headerInput = screen.getByPlaceholderText('tools.createTool.authMethod.types.apiKeyPlaceholder')
|
||||
expect(headerInput).toHaveValue('X-Custom-Header')
|
||||
})
|
||||
|
||||
it('should show apiKeyQuery fields when initial auth type is apiKeyQuery', async () => {
|
||||
const queryCredential: Credential = {
|
||||
auth_type: AuthType.apiKeyQuery,
|
||||
api_key_query_param: 'apikey',
|
||||
api_key_value: 'queryvalue',
|
||||
}
|
||||
|
||||
await act(async () => {
|
||||
render(
|
||||
<ConfigCredential
|
||||
credential={queryCredential}
|
||||
onChange={mockOnChange}
|
||||
onHide={mockOnHide}
|
||||
/>,
|
||||
)
|
||||
})
|
||||
|
||||
// Query param input should be visible with initial value
|
||||
const queryParamInput = screen.getByPlaceholderText('tools.createTool.authMethod.types.queryParamPlaceholder')
|
||||
expect(queryParamInput).toHaveValue('apikey')
|
||||
})
|
||||
expect(mockOnHide).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
import type { ModalContextState } from '@/context/modal-context'
|
||||
import type { ProviderContextState } from '@/context/provider-context'
|
||||
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import { Plan } from '@/app/components/billing/type'
|
||||
import { AuthHeaderPrefix, AuthType } from '@/app/components/tools/types'
|
||||
import { parseParamsSchema } from '@/service/tools'
|
||||
import EditCustomCollectionModal from './index'
|
||||
|
||||
@ -52,6 +54,18 @@ vi.mock('@/context/i18n', async () => {
|
||||
}
|
||||
})
|
||||
|
||||
// Mock EmojiPicker
|
||||
vi.mock('@/app/components/base/emoji-picker', () => ({
|
||||
default: ({ onSelect, onClose }: { onSelect: (icon: string, background: string) => void, onClose: () => void }) => {
|
||||
return (
|
||||
<div data-testid="emoji-picker">
|
||||
<button data-testid="select-emoji" onClick={() => onSelect('🚀', '#FF0000')}>Select Emoji</button>
|
||||
<button data-testid="close-emoji-picker" onClick={onClose}>Close</button>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}))
|
||||
|
||||
describe('EditCustomCollectionModal', () => {
|
||||
const mockOnHide = vi.fn()
|
||||
const mockOnAdd = vi.fn()
|
||||
@ -75,80 +89,490 @@ describe('EditCustomCollectionModal', () => {
|
||||
} as ProviderContextState)
|
||||
})
|
||||
|
||||
const renderModal = () => render(
|
||||
const renderModal = (props?: {
|
||||
payload?: {
|
||||
provider: string
|
||||
credentials: { auth_type: AuthType, api_key_header?: string, api_key_header_prefix?: AuthHeaderPrefix, api_key_value?: string }
|
||||
schema_type: string
|
||||
schema: string
|
||||
icon: { content: string, background: string }
|
||||
privacy_policy?: string
|
||||
custom_disclaimer?: string
|
||||
labels?: string[]
|
||||
tools?: Array<{ operation_id: string, summary: string, method: string, server_url: string, parameters: Array<{ name: string, label: { en_US: string, zh_Hans: string } }> }>
|
||||
}
|
||||
positionLeft?: boolean
|
||||
dialogClassName?: string
|
||||
}) => render(
|
||||
<EditCustomCollectionModal
|
||||
payload={undefined}
|
||||
payload={props?.payload}
|
||||
onHide={mockOnHide}
|
||||
onAdd={mockOnAdd}
|
||||
onEdit={mockOnEdit}
|
||||
onRemove={mockOnRemove}
|
||||
positionLeft={props?.positionLeft}
|
||||
dialogClassName={props?.dialogClassName}
|
||||
/>,
|
||||
)
|
||||
|
||||
it('shows an error when the provider name is missing', async () => {
|
||||
renderModal()
|
||||
// Tests for Add mode (no payload)
|
||||
describe('Add Mode', () => {
|
||||
it('should render add mode title when no payload', () => {
|
||||
renderModal()
|
||||
|
||||
const schemaInput = screen.getByPlaceholderText('tools.createTool.schemaPlaceHolder')
|
||||
fireEvent.change(schemaInput, { target: { value: '{}' } })
|
||||
await waitFor(() => {
|
||||
expect(parseParamsSchemaMock).toHaveBeenCalledWith('{}')
|
||||
expect(screen.getByText('tools.createTool.title')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
fireEvent.click(screen.getByText('common.operation.save'))
|
||||
it('should show error when provider name is missing', async () => {
|
||||
renderModal()
|
||||
|
||||
await waitFor(() => {
|
||||
expect(toastNotifySpy).toHaveBeenCalledWith(expect.objectContaining({
|
||||
message: 'common.errorMsg.fieldRequired:{"field":"tools.createTool.name"}',
|
||||
type: 'error',
|
||||
}))
|
||||
})
|
||||
expect(mockOnAdd).not.toHaveBeenCalled()
|
||||
})
|
||||
const schemaInput = screen.getByPlaceholderText('tools.createTool.schemaPlaceHolder')
|
||||
fireEvent.change(schemaInput, { target: { value: '{}' } })
|
||||
await waitFor(() => {
|
||||
expect(parseParamsSchemaMock).toHaveBeenCalledWith('{}')
|
||||
})
|
||||
|
||||
it('shows an error when the schema is missing', async () => {
|
||||
renderModal()
|
||||
|
||||
const providerInput = screen.getByPlaceholderText('tools.createTool.toolNamePlaceHolder')
|
||||
fireEvent.change(providerInput, { target: { value: 'provider' } })
|
||||
|
||||
fireEvent.click(screen.getByText('common.operation.save'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(toastNotifySpy).toHaveBeenCalledWith(expect.objectContaining({
|
||||
message: 'common.errorMsg.fieldRequired:{"field":"tools.createTool.schema"}',
|
||||
type: 'error',
|
||||
}))
|
||||
})
|
||||
expect(mockOnAdd).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('saves a valid custom collection', async () => {
|
||||
renderModal()
|
||||
const providerInput = screen.getByPlaceholderText('tools.createTool.toolNamePlaceHolder')
|
||||
fireEvent.change(providerInput, { target: { value: 'provider' } })
|
||||
|
||||
const schemaInput = screen.getByPlaceholderText('tools.createTool.schemaPlaceHolder')
|
||||
fireEvent.change(schemaInput, { target: { value: '{}' } })
|
||||
|
||||
await waitFor(() => {
|
||||
expect(parseParamsSchemaMock).toHaveBeenCalledWith('{}')
|
||||
})
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(screen.getByText('common.operation.save'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(toastNotifySpy).toHaveBeenCalledWith(expect.objectContaining({
|
||||
message: 'common.errorMsg.fieldRequired:{"field":"tools.createTool.name"}',
|
||||
type: 'error',
|
||||
}))
|
||||
})
|
||||
expect(mockOnAdd).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockOnAdd).toHaveBeenCalledWith(expect.objectContaining({
|
||||
provider: 'provider',
|
||||
schema: '{}',
|
||||
schema_type: 'openapi',
|
||||
it('should show error when schema is missing', async () => {
|
||||
renderModal()
|
||||
|
||||
const providerInput = screen.getByPlaceholderText('tools.createTool.toolNamePlaceHolder')
|
||||
fireEvent.change(providerInput, { target: { value: 'provider' } })
|
||||
|
||||
fireEvent.click(screen.getByText('common.operation.save'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(toastNotifySpy).toHaveBeenCalledWith(expect.objectContaining({
|
||||
message: 'common.errorMsg.fieldRequired:{"field":"tools.createTool.schema"}',
|
||||
type: 'error',
|
||||
}))
|
||||
})
|
||||
expect(mockOnAdd).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should save a valid custom collection', async () => {
|
||||
renderModal()
|
||||
const providerInput = screen.getByPlaceholderText('tools.createTool.toolNamePlaceHolder')
|
||||
fireEvent.change(providerInput, { target: { value: 'provider' } })
|
||||
|
||||
const schemaInput = screen.getByPlaceholderText('tools.createTool.schemaPlaceHolder')
|
||||
fireEvent.change(schemaInput, { target: { value: '{}' } })
|
||||
|
||||
await waitFor(() => {
|
||||
expect(parseParamsSchemaMock).toHaveBeenCalledWith('{}')
|
||||
})
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(screen.getByText('common.operation.save'))
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockOnAdd).toHaveBeenCalledWith(expect.objectContaining({
|
||||
provider: 'provider',
|
||||
schema: '{}',
|
||||
schema_type: 'openapi',
|
||||
credentials: {
|
||||
auth_type: 'none',
|
||||
},
|
||||
labels: [],
|
||||
}))
|
||||
expect(toastNotifySpy).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
it('should call onHide when cancel is clicked', () => {
|
||||
renderModal()
|
||||
|
||||
fireEvent.click(screen.getByText('common.operation.cancel'))
|
||||
|
||||
expect(mockOnHide).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
// Tests for Edit mode (with payload)
|
||||
describe('Edit Mode', () => {
|
||||
const editPayload = {
|
||||
provider: 'existing-provider',
|
||||
credentials: {
|
||||
auth_type: AuthType.apiKeyHeader,
|
||||
api_key_header: 'X-Api-Key',
|
||||
api_key_header_prefix: AuthHeaderPrefix.bearer,
|
||||
api_key_value: 'secret-key',
|
||||
},
|
||||
schema_type: 'openapi',
|
||||
schema: '{"openapi": "3.0.0"}',
|
||||
icon: { content: '🔧', background: '#FFCC00' },
|
||||
privacy_policy: 'https://example.com/privacy',
|
||||
custom_disclaimer: 'Use at your own risk',
|
||||
labels: ['api', 'tools'],
|
||||
tools: [{
|
||||
operation_id: 'getUsers',
|
||||
summary: 'Get all users',
|
||||
method: 'GET',
|
||||
server_url: 'https://api.example.com/users',
|
||||
parameters: [{
|
||||
name: 'limit',
|
||||
label: { en_US: 'Limit', zh_Hans: '限制' },
|
||||
}],
|
||||
}],
|
||||
}
|
||||
|
||||
it('should render edit mode title when payload is provided', () => {
|
||||
renderModal({ payload: editPayload })
|
||||
|
||||
expect(screen.getByText('tools.createTool.editTitle')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show delete button in edit mode', () => {
|
||||
renderModal({ payload: editPayload })
|
||||
|
||||
expect(screen.getByText('common.operation.delete')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should call onRemove when delete button is clicked', () => {
|
||||
renderModal({ payload: editPayload })
|
||||
|
||||
fireEvent.click(screen.getByText('common.operation.delete'))
|
||||
|
||||
expect(mockOnRemove).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should call onEdit with original_provider when saving in edit mode', async () => {
|
||||
renderModal({ payload: editPayload })
|
||||
|
||||
// Change the provider name
|
||||
const providerInput = screen.getByPlaceholderText('tools.createTool.toolNamePlaceHolder')
|
||||
fireEvent.change(providerInput, { target: { value: 'updated-provider' } })
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(screen.getByText('common.operation.save'))
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockOnEdit).toHaveBeenCalledWith(expect.objectContaining({
|
||||
provider: 'updated-provider',
|
||||
original_provider: 'existing-provider',
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
it('should display existing provider name', () => {
|
||||
renderModal({ payload: editPayload })
|
||||
|
||||
const providerInput = screen.getByPlaceholderText('tools.createTool.toolNamePlaceHolder')
|
||||
expect(providerInput).toHaveValue('existing-provider')
|
||||
})
|
||||
|
||||
it('should display existing schema', () => {
|
||||
renderModal({ payload: editPayload })
|
||||
|
||||
const schemaInput = screen.getByPlaceholderText('tools.createTool.schemaPlaceHolder')
|
||||
expect(schemaInput).toHaveValue('{"openapi": "3.0.0"}')
|
||||
})
|
||||
|
||||
it('should display available tools table', () => {
|
||||
renderModal({ payload: editPayload })
|
||||
|
||||
expect(screen.getByText('getUsers')).toBeInTheDocument()
|
||||
expect(screen.getByText('Get all users')).toBeInTheDocument()
|
||||
expect(screen.getByText('GET')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should strip credential fields when auth_type is none on save', async () => {
|
||||
const payloadWithNoneAuth = {
|
||||
...editPayload,
|
||||
credentials: {
|
||||
auth_type: 'none',
|
||||
auth_type: AuthType.none,
|
||||
api_key_header: 'should-be-removed',
|
||||
api_key_header_prefix: AuthHeaderPrefix.bearer,
|
||||
api_key_value: 'should-be-removed',
|
||||
},
|
||||
labels: [],
|
||||
}))
|
||||
expect(toastNotifySpy).not.toHaveBeenCalled()
|
||||
}
|
||||
|
||||
renderModal({ payload: payloadWithNoneAuth })
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(screen.getByText('common.operation.save'))
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockOnEdit).toHaveBeenCalledWith(expect.objectContaining({
|
||||
credentials: {
|
||||
auth_type: AuthType.none,
|
||||
},
|
||||
}))
|
||||
// These fields should NOT be present
|
||||
const callArg = mockOnEdit.mock.calls[0][0]
|
||||
expect(callArg.credentials.api_key_header).toBeUndefined()
|
||||
expect(callArg.credentials.api_key_header_prefix).toBeUndefined()
|
||||
expect(callArg.credentials.api_key_value).toBeUndefined()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Tests for Schema parsing
|
||||
describe('Schema Parsing', () => {
|
||||
it('should parse schema and update params when schema changes', async () => {
|
||||
parseParamsSchemaMock.mockResolvedValueOnce({
|
||||
parameters_schema: [{
|
||||
operation_id: 'newOp',
|
||||
summary: 'New operation',
|
||||
method: 'POST',
|
||||
server_url: 'https://api.example.com/new',
|
||||
parameters: [],
|
||||
}],
|
||||
schema_type: 'swagger',
|
||||
})
|
||||
|
||||
renderModal()
|
||||
|
||||
const schemaInput = screen.getByPlaceholderText('tools.createTool.schemaPlaceHolder')
|
||||
fireEvent.change(schemaInput, { target: { value: '{"swagger": "2.0"}' } })
|
||||
|
||||
await waitFor(() => {
|
||||
expect(parseParamsSchemaMock).toHaveBeenCalledWith('{"swagger": "2.0"}')
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('newOp')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('should handle schema parse error and reset params', async () => {
|
||||
parseParamsSchemaMock.mockRejectedValueOnce(new Error('Parse error'))
|
||||
|
||||
renderModal()
|
||||
|
||||
const schemaInput = screen.getByPlaceholderText('tools.createTool.schemaPlaceHolder')
|
||||
fireEvent.change(schemaInput, { target: { value: 'invalid schema' } })
|
||||
|
||||
await waitFor(() => {
|
||||
expect(parseParamsSchemaMock).toHaveBeenCalledWith('invalid schema')
|
||||
})
|
||||
|
||||
// The table should still be visible but empty (no tools)
|
||||
expect(screen.getByText('tools.createTool.availableTools.title')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should not parse schema when empty', async () => {
|
||||
renderModal()
|
||||
|
||||
// Clear any calls from initial render
|
||||
parseParamsSchemaMock.mockClear()
|
||||
|
||||
const schemaInput = screen.getByPlaceholderText('tools.createTool.schemaPlaceHolder')
|
||||
fireEvent.change(schemaInput, { target: { value: '' } })
|
||||
|
||||
// Wait a bit and check that parseParamsSchema was not called with empty string
|
||||
await new Promise(resolve => setTimeout(resolve, 100))
|
||||
expect(parseParamsSchemaMock).not.toHaveBeenCalledWith('')
|
||||
})
|
||||
})
|
||||
|
||||
// Tests for Icon Section
|
||||
describe('Icon Section', () => {
|
||||
it('should render icon section', () => {
|
||||
renderModal()
|
||||
|
||||
// The name input should be present
|
||||
const nameInput = screen.getByPlaceholderText('tools.createTool.toolNamePlaceHolder')
|
||||
expect(nameInput).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render name input section', () => {
|
||||
renderModal()
|
||||
|
||||
// Name label should be present
|
||||
expect(screen.getByText('tools.createTool.name')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
// Tests for Credentials Modal
|
||||
describe('Credentials Modal', () => {
|
||||
it('should show auth method section title', () => {
|
||||
renderModal()
|
||||
|
||||
expect(screen.getByText('tools.createTool.authMethod.title')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should display current auth type', () => {
|
||||
renderModal()
|
||||
|
||||
// The default auth type is 'none'
|
||||
expect(screen.getByText('tools.createTool.authMethod.types.none')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
// Tests for Test API Modal
|
||||
describe('Test API Modal', () => {
|
||||
const payloadWithTools = {
|
||||
provider: 'test-provider',
|
||||
credentials: { auth_type: AuthType.none },
|
||||
schema_type: 'openapi',
|
||||
schema: '{}',
|
||||
icon: { content: '🔧', background: '#FFCC00' },
|
||||
tools: [{
|
||||
operation_id: 'testOp',
|
||||
summary: 'Test operation',
|
||||
method: 'POST',
|
||||
server_url: 'https://api.example.com/test',
|
||||
parameters: [],
|
||||
}],
|
||||
}
|
||||
|
||||
it('should render test button in available tools table', () => {
|
||||
renderModal({ payload: payloadWithTools })
|
||||
|
||||
// Find the test button
|
||||
const testButton = screen.getByText('tools.createTool.availableTools.test')
|
||||
expect(testButton).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should display tool information in the table', () => {
|
||||
renderModal({ payload: payloadWithTools })
|
||||
|
||||
expect(screen.getByText('testOp')).toBeInTheDocument()
|
||||
expect(screen.getByText('Test operation')).toBeInTheDocument()
|
||||
expect(screen.getByText('POST')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
// Tests for Privacy Policy and Custom Disclaimer
|
||||
describe('Privacy Policy and Custom Disclaimer', () => {
|
||||
it('should update privacy policy input', () => {
|
||||
renderModal()
|
||||
|
||||
const privacyInput = screen.getByPlaceholderText('tools.createTool.privacyPolicyPlaceholder')
|
||||
fireEvent.change(privacyInput, { target: { value: 'https://example.com/privacy' } })
|
||||
|
||||
expect(privacyInput).toHaveValue('https://example.com/privacy')
|
||||
})
|
||||
|
||||
it('should update custom disclaimer input', () => {
|
||||
renderModal()
|
||||
|
||||
const disclaimerInput = screen.getByPlaceholderText('tools.createTool.customDisclaimerPlaceholder')
|
||||
fireEvent.change(disclaimerInput, { target: { value: 'Custom disclaimer text' } })
|
||||
|
||||
expect(disclaimerInput).toHaveValue('Custom disclaimer text')
|
||||
})
|
||||
|
||||
it('should include privacy policy and custom disclaimer in save payload', async () => {
|
||||
renderModal()
|
||||
|
||||
const providerInput = screen.getByPlaceholderText('tools.createTool.toolNamePlaceHolder')
|
||||
fireEvent.change(providerInput, { target: { value: 'test-provider' } })
|
||||
|
||||
const schemaInput = screen.getByPlaceholderText('tools.createTool.schemaPlaceHolder')
|
||||
fireEvent.change(schemaInput, { target: { value: '{}' } })
|
||||
|
||||
const privacyInput = screen.getByPlaceholderText('tools.createTool.privacyPolicyPlaceholder')
|
||||
fireEvent.change(privacyInput, { target: { value: 'https://privacy.example.com' } })
|
||||
|
||||
const disclaimerInput = screen.getByPlaceholderText('tools.createTool.customDisclaimerPlaceholder')
|
||||
fireEvent.change(disclaimerInput, { target: { value: 'My disclaimer' } })
|
||||
|
||||
await waitFor(() => {
|
||||
expect(parseParamsSchemaMock).toHaveBeenCalledWith('{}')
|
||||
})
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(screen.getByText('common.operation.save'))
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockOnAdd).toHaveBeenCalledWith(expect.objectContaining({
|
||||
privacy_policy: 'https://privacy.example.com',
|
||||
custom_disclaimer: 'My disclaimer',
|
||||
}))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Tests for Props
|
||||
describe('Props', () => {
|
||||
it('should render with positionLeft prop', () => {
|
||||
renderModal({ positionLeft: true })
|
||||
|
||||
expect(screen.getByText('tools.createTool.title')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render with dialogClassName prop', () => {
|
||||
renderModal({ dialogClassName: 'custom-dialog-class' })
|
||||
|
||||
expect(screen.getByText('tools.createTool.title')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
// Tests for getPath helper function
|
||||
describe('URL Path Extraction', () => {
|
||||
const payloadWithVariousUrls = (serverUrl: string) => ({
|
||||
provider: 'test-provider',
|
||||
credentials: { auth_type: AuthType.none },
|
||||
schema_type: 'openapi',
|
||||
schema: '{}',
|
||||
icon: { content: '🔧', background: '#FFCC00' },
|
||||
tools: [{
|
||||
operation_id: 'testOp',
|
||||
summary: 'Test',
|
||||
method: 'GET',
|
||||
server_url: serverUrl,
|
||||
parameters: [],
|
||||
}],
|
||||
})
|
||||
|
||||
it('should extract path from full URL', () => {
|
||||
renderModal({ payload: payloadWithVariousUrls('https://api.example.com/users/list') })
|
||||
|
||||
expect(screen.getByText('/users/list')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle URL with encoded characters', () => {
|
||||
renderModal({ payload: payloadWithVariousUrls('https://api.example.com/users%20list') })
|
||||
|
||||
expect(screen.getByText('/users list')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle empty URL', () => {
|
||||
renderModal({ payload: payloadWithVariousUrls('') })
|
||||
|
||||
// Should not crash and show the row
|
||||
expect(screen.getByText('testOp')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle invalid URL by returning the original string', () => {
|
||||
renderModal({ payload: payloadWithVariousUrls('not-a-valid-url') })
|
||||
|
||||
// Should show the original string
|
||||
expect(screen.getByText('not-a-valid-url')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle URL with only domain', () => {
|
||||
renderModal({ payload: payloadWithVariousUrls('https://api.example.com') })
|
||||
|
||||
// Path would be empty or "/"
|
||||
expect(screen.getByText('testOp')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
// Tests for Schema spec link
|
||||
describe('Schema Spec Link', () => {
|
||||
it('should render swagger spec link', () => {
|
||||
renderModal()
|
||||
|
||||
const link = screen.getByText('tools.createTool.viewSchemaSpec')
|
||||
expect(link.closest('a')).toHaveAttribute('href', 'https://swagger.io/specification/')
|
||||
expect(link.closest('a')).toHaveAttribute('target', '_blank')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import type { CustomCollectionBackend, CustomParamSchema } from '@/app/components/tools/types'
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import { AuthType } from '@/app/components/tools/types'
|
||||
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { AuthHeaderPrefix, AuthType } from '@/app/components/tools/types'
|
||||
import { testAPIAvailable } from '@/service/tools'
|
||||
import TestApi from './test-api'
|
||||
|
||||
@ -28,6 +29,7 @@ describe('TestApi', () => {
|
||||
id: 'test-id',
|
||||
labels: [],
|
||||
}
|
||||
|
||||
const tool: CustomParamSchema = {
|
||||
operation_id: 'testOp',
|
||||
summary: 'summary',
|
||||
@ -39,46 +41,305 @@ describe('TestApi', () => {
|
||||
en_US: 'Limit',
|
||||
zh_Hans: '限制',
|
||||
},
|
||||
// eslint-disable-next-line ts/no-explicit-any
|
||||
} as any],
|
||||
} as CustomParamSchema['parameters'][0]],
|
||||
}
|
||||
|
||||
const renderTestApi = () => {
|
||||
const mockOnHide = vi.fn()
|
||||
|
||||
const renderTestApi = (props?: {
|
||||
customCollection?: CustomCollectionBackend
|
||||
tool?: CustomParamSchema
|
||||
positionCenter?: boolean
|
||||
}) => {
|
||||
return render(
|
||||
<TestApi
|
||||
customCollection={customCollection}
|
||||
tool={tool}
|
||||
onHide={vi.fn()}
|
||||
customCollection={props?.customCollection ?? customCollection}
|
||||
tool={props?.tool ?? tool}
|
||||
onHide={props ? mockOnHide : vi.fn()}
|
||||
positionCenter={props?.positionCenter}
|
||||
/>,
|
||||
)
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
testAPIAvailableMock.mockReset()
|
||||
})
|
||||
|
||||
it('renders parameters and runs the API test', async () => {
|
||||
testAPIAvailableMock.mockResolvedValueOnce({ result: 'ok' })
|
||||
renderTestApi()
|
||||
// Tests for basic rendering
|
||||
describe('Rendering', () => {
|
||||
it('should render without crashing', async () => {
|
||||
await act(async () => {
|
||||
renderTestApi()
|
||||
})
|
||||
|
||||
const parameterInput = screen.getAllByRole('textbox')[0]
|
||||
fireEvent.change(parameterInput, { target: { value: '5' } })
|
||||
fireEvent.click(screen.getByRole('button', { name: 'tools.test.title' }))
|
||||
expect(screen.getByText('tools.test.testResult')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(testAPIAvailableMock).toHaveBeenCalledWith({
|
||||
provider_name: customCollection.provider,
|
||||
tool_name: tool.operation_id,
|
||||
it('should display tool name in the title', async () => {
|
||||
await act(async () => {
|
||||
renderTestApi()
|
||||
})
|
||||
|
||||
expect(screen.getByText(/testOp/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render parameters table', async () => {
|
||||
await act(async () => {
|
||||
renderTestApi()
|
||||
})
|
||||
|
||||
expect(screen.getByText('tools.test.parameters')).toBeInTheDocument()
|
||||
expect(screen.getByText('tools.test.value')).toBeInTheDocument()
|
||||
expect(screen.getByText('Limit')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render test result placeholder', async () => {
|
||||
await act(async () => {
|
||||
renderTestApi()
|
||||
})
|
||||
|
||||
expect(screen.getByText('tools.test.testResultPlaceholder')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render with positionCenter prop', async () => {
|
||||
await act(async () => {
|
||||
renderTestApi({ positionCenter: true })
|
||||
})
|
||||
|
||||
expect(screen.getByText('tools.test.testResult')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
// Tests for API test execution
|
||||
describe('API Test Execution', () => {
|
||||
it('should run API test with parameters and show result', async () => {
|
||||
testAPIAvailableMock.mockResolvedValueOnce({ result: 'ok' })
|
||||
renderTestApi()
|
||||
|
||||
const parameterInput = screen.getAllByRole('textbox')[0]
|
||||
fireEvent.change(parameterInput, { target: { value: '5' } })
|
||||
fireEvent.click(screen.getByRole('button', { name: 'tools.test.title' }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(testAPIAvailableMock).toHaveBeenCalledWith({
|
||||
provider_name: customCollection.provider,
|
||||
tool_name: tool.operation_id,
|
||||
credentials: {
|
||||
auth_type: AuthType.none,
|
||||
},
|
||||
schema_type: customCollection.schema_type,
|
||||
schema: customCollection.schema,
|
||||
parameters: {
|
||||
limit: '5',
|
||||
},
|
||||
})
|
||||
expect(screen.getByText('ok')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('should display error result when API returns error', async () => {
|
||||
testAPIAvailableMock.mockResolvedValueOnce({ error: 'API Error occurred' })
|
||||
renderTestApi()
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'tools.test.title' }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('API Error occurred')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('should call API when test button is clicked', async () => {
|
||||
testAPIAvailableMock.mockResolvedValueOnce({ result: 'test completed' })
|
||||
|
||||
await act(async () => {
|
||||
renderTestApi()
|
||||
})
|
||||
|
||||
// Click test button
|
||||
await act(async () => {
|
||||
fireEvent.click(screen.getByRole('button', { name: 'tools.test.title' }))
|
||||
})
|
||||
|
||||
// API should have been called
|
||||
await waitFor(() => {
|
||||
expect(testAPIAvailableMock).toHaveBeenCalledTimes(1)
|
||||
expect(screen.getByText('test completed')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('should strip extra credential fields when auth_type is none', async () => {
|
||||
const collectionWithExtraFields: CustomCollectionBackend = {
|
||||
...customCollection,
|
||||
credentials: {
|
||||
auth_type: AuthType.none,
|
||||
api_key_header: 'X-Api-Key',
|
||||
api_key_header_prefix: AuthHeaderPrefix.bearer,
|
||||
api_key_value: 'secret',
|
||||
},
|
||||
schema_type: customCollection.schema_type,
|
||||
schema: customCollection.schema,
|
||||
parameters: {
|
||||
limit: '5',
|
||||
},
|
||||
}
|
||||
|
||||
testAPIAvailableMock.mockResolvedValueOnce({ result: 'success' })
|
||||
renderTestApi({ customCollection: collectionWithExtraFields })
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'tools.test.title' }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(testAPIAvailableMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
credentials: {
|
||||
auth_type: AuthType.none,
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Tests for credentials modal
|
||||
describe('Credentials Modal', () => {
|
||||
it('should show auth method display text', async () => {
|
||||
await act(async () => {
|
||||
renderTestApi()
|
||||
})
|
||||
|
||||
// Check that the auth method is displayed
|
||||
expect(screen.getByText('tools.createTool.authMethod.types.none')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should display current auth type in the button', async () => {
|
||||
const collectionWithHeader: CustomCollectionBackend = {
|
||||
...customCollection,
|
||||
credentials: {
|
||||
auth_type: AuthType.apiKeyHeader,
|
||||
api_key_header: 'X-Api-Key',
|
||||
api_key_header_prefix: AuthHeaderPrefix.bearer,
|
||||
api_key_value: 'token',
|
||||
},
|
||||
}
|
||||
|
||||
await act(async () => {
|
||||
renderTestApi({ customCollection: collectionWithHeader })
|
||||
})
|
||||
|
||||
// Check that the auth method display shows the correct type
|
||||
expect(screen.getByText('tools.createTool.authMethod.types.api_key_header')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
// Tests for multiple parameters
|
||||
describe('Multiple Parameters', () => {
|
||||
it('should handle multiple parameters', async () => {
|
||||
const toolWithMultipleParams: CustomParamSchema = {
|
||||
...tool,
|
||||
parameters: [
|
||||
{
|
||||
name: 'limit',
|
||||
label: { en_US: 'Limit', zh_Hans: '限制' },
|
||||
} as CustomParamSchema['parameters'][0],
|
||||
{
|
||||
name: 'offset',
|
||||
label: { en_US: 'Offset', zh_Hans: '偏移' },
|
||||
} as CustomParamSchema['parameters'][0],
|
||||
],
|
||||
}
|
||||
|
||||
testAPIAvailableMock.mockResolvedValueOnce({ result: 'multi-param success' })
|
||||
renderTestApi({ tool: toolWithMultipleParams })
|
||||
|
||||
const inputs = screen.getAllByRole('textbox')
|
||||
fireEvent.change(inputs[0], { target: { value: '10' } })
|
||||
fireEvent.change(inputs[1], { target: { value: '20' } })
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'tools.test.title' }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(testAPIAvailableMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
parameters: {
|
||||
limit: '10',
|
||||
offset: '20',
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('should handle empty parameters', async () => {
|
||||
testAPIAvailableMock.mockResolvedValueOnce({ result: 'empty params success' })
|
||||
renderTestApi()
|
||||
|
||||
// Don't fill in any parameters
|
||||
fireEvent.click(screen.getByRole('button', { name: 'tools.test.title' }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(testAPIAvailableMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
parameters: {},
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Tests for different auth types
|
||||
describe('Different Auth Types', () => {
|
||||
it('should pass apiKeyHeader credentials to API', async () => {
|
||||
const collectionWithHeader: CustomCollectionBackend = {
|
||||
...customCollection,
|
||||
credentials: {
|
||||
auth_type: AuthType.apiKeyHeader,
|
||||
api_key_header: 'Authorization',
|
||||
api_key_header_prefix: AuthHeaderPrefix.bearer,
|
||||
api_key_value: 'test-token',
|
||||
},
|
||||
}
|
||||
|
||||
testAPIAvailableMock.mockResolvedValueOnce({ result: 'header auth success' })
|
||||
renderTestApi({ customCollection: collectionWithHeader })
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'tools.test.title' }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(testAPIAvailableMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
credentials: {
|
||||
auth_type: AuthType.apiKeyHeader,
|
||||
api_key_header: 'Authorization',
|
||||
api_key_header_prefix: AuthHeaderPrefix.bearer,
|
||||
api_key_value: 'test-token',
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('should pass apiKeyQuery credentials to API', async () => {
|
||||
const collectionWithQuery: CustomCollectionBackend = {
|
||||
...customCollection,
|
||||
credentials: {
|
||||
auth_type: AuthType.apiKeyQuery,
|
||||
api_key_query_param: 'api_key',
|
||||
api_key_value: 'query-token',
|
||||
},
|
||||
}
|
||||
|
||||
testAPIAvailableMock.mockResolvedValueOnce({ result: 'query auth success' })
|
||||
renderTestApi({ customCollection: collectionWithQuery })
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'tools.test.title' }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(testAPIAvailableMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
credentials: {
|
||||
auth_type: AuthType.apiKeyQuery,
|
||||
api_key_query_param: 'api_key',
|
||||
api_key_value: 'query-token',
|
||||
},
|
||||
}),
|
||||
)
|
||||
})
|
||||
expect(screen.getByText('ok')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user