mirror of https://github.com/langgenius/dify.git
346 lines
10 KiB
TypeScript
346 lines
10 KiB
TypeScript
import type { CustomCollectionBackend, CustomParamSchema } 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'
|
|
|
|
vi.mock('@/service/tools', () => ({
|
|
testAPIAvailable: vi.fn(),
|
|
}))
|
|
|
|
vi.mock('@/context/i18n', () => ({
|
|
useLocale: vi.fn(() => 'en-US'),
|
|
}))
|
|
|
|
const testAPIAvailableMock = vi.mocked(testAPIAvailable)
|
|
|
|
describe('TestApi', () => {
|
|
const customCollection: CustomCollectionBackend = {
|
|
provider: 'custom',
|
|
credentials: {
|
|
auth_type: AuthType.none,
|
|
},
|
|
schema_type: 'openapi',
|
|
schema: '{ }',
|
|
icon: { background: '', content: '' },
|
|
privacy_policy: '',
|
|
custom_disclaimer: '',
|
|
id: 'test-id',
|
|
labels: [],
|
|
}
|
|
|
|
const tool: CustomParamSchema = {
|
|
operation_id: 'testOp',
|
|
summary: 'summary',
|
|
method: 'GET',
|
|
server_url: 'https://api.example.com',
|
|
parameters: [{
|
|
name: 'limit',
|
|
label: {
|
|
en_US: 'Limit',
|
|
zh_Hans: '限制',
|
|
},
|
|
} as CustomParamSchema['parameters'][0]],
|
|
}
|
|
|
|
const mockOnHide = vi.fn()
|
|
|
|
const renderTestApi = (props?: {
|
|
customCollection?: CustomCollectionBackend
|
|
tool?: CustomParamSchema
|
|
positionCenter?: boolean
|
|
}) => {
|
|
return render(
|
|
<TestApi
|
|
customCollection={props?.customCollection ?? customCollection}
|
|
tool={props?.tool ?? tool}
|
|
onHide={props ? mockOnHide : vi.fn()}
|
|
positionCenter={props?.positionCenter}
|
|
/>,
|
|
)
|
|
}
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
testAPIAvailableMock.mockReset()
|
|
})
|
|
|
|
// Tests for basic rendering
|
|
describe('Rendering', () => {
|
|
it('should render without crashing', async () => {
|
|
await act(async () => {
|
|
renderTestApi()
|
|
})
|
|
|
|
expect(screen.getByText('tools.test.testResult')).toBeInTheDocument()
|
|
})
|
|
|
|
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',
|
|
},
|
|
}
|
|
|
|
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',
|
|
},
|
|
}),
|
|
)
|
|
})
|
|
})
|
|
})
|
|
})
|