chore: some test (#30148)

Co-authored-by: yyh <92089059+lyzno1@users.noreply.github.com>
This commit is contained in:
Joel
2025-12-25 19:45:27 +08:00
committed by GitHub
parent 08d5eee993
commit 0f3ffbee2c
7 changed files with 747 additions and 0 deletions

View File

@ -0,0 +1,60 @@
import type { Credential } from '@/app/components/tools/types'
import { act, fireEvent, render, screen } from '@testing-library/react'
import { AuthHeaderPrefix, AuthType } from '@/app/components/tools/types'
import ConfigCredential from './config-credentials'
describe('ConfigCredential', () => {
const baseCredential: Credential = {
auth_type: AuthType.none,
}
const mockOnChange = vi.fn()
const mockOnHide = vi.fn()
beforeEach(() => {
vi.clearAllMocks()
})
it('renders and calls 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('allows selecting apiKeyHeader and submits the new credential', 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()
})
})

View File

@ -0,0 +1,55 @@
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import { importSchemaFromURL } from '@/service/tools'
import Toast from '../../base/toast'
import examples from './examples'
import GetSchema from './get-schema'
vi.mock('@/service/tools', () => ({
importSchemaFromURL: vi.fn(),
}))
const importSchemaFromURLMock = vi.mocked(importSchemaFromURL)
describe('GetSchema', () => {
const notifySpy = vi.spyOn(Toast, 'notify')
const mockOnChange = vi.fn()
beforeEach(() => {
vi.clearAllMocks()
notifySpy.mockClear()
importSchemaFromURLMock.mockReset()
render(<GetSchema onChange={mockOnChange} />)
})
it('shows an error when the URL is not http', () => {
fireEvent.click(screen.getByText('tools.createTool.importFromUrl'))
const input = screen.getByPlaceholderText('tools.createTool.importFromUrlPlaceHolder')
// eslint-disable-next-line sonarjs/no-clear-text-protocols
fireEvent.change(input, { target: { value: 'ftp://invalid' } })
fireEvent.click(screen.getByText('common.operation.ok'))
expect(notifySpy).toHaveBeenCalledWith({
type: 'error',
message: 'tools.createTool.urlError',
})
})
it('imports schema from url when valid', async () => {
fireEvent.click(screen.getByText('tools.createTool.importFromUrl'))
const input = screen.getByPlaceholderText('tools.createTool.importFromUrlPlaceHolder')
fireEvent.change(input, { target: { value: 'https://example.com' } })
importSchemaFromURLMock.mockResolvedValueOnce({ schema: 'result-schema' })
fireEvent.click(screen.getByText('common.operation.ok'))
await waitFor(() => {
expect(mockOnChange).toHaveBeenCalledWith('result-schema')
})
})
it('selects example schema when example option clicked', () => {
fireEvent.click(screen.getByText('tools.createTool.examples'))
fireEvent.click(screen.getByText(`tools.createTool.exampleOptions.${examples[0].key}`))
expect(mockOnChange).toHaveBeenCalledWith(examples[0].content)
})
})

View File

@ -0,0 +1,154 @@
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 Toast from '@/app/components/base/toast'
import { Plan } from '@/app/components/billing/type'
import { parseParamsSchema } from '@/service/tools'
import EditCustomCollectionModal from './index'
vi.mock('ahooks', async () => {
const actual = await vi.importActual<typeof import('ahooks')>('ahooks')
return {
...actual,
useDebounce: (value: unknown) => value,
}
})
vi.mock('@/service/tools', () => ({
parseParamsSchema: vi.fn(),
}))
const parseParamsSchemaMock = vi.mocked(parseParamsSchema)
const mockSetShowPricingModal = vi.fn()
const mockSetShowAccountSettingModal = vi.fn()
vi.mock('@/context/modal-context', () => ({
useModalContext: (): ModalContextState => ({
setShowAccountSettingModal: mockSetShowAccountSettingModal,
setShowApiBasedExtensionModal: vi.fn(),
setShowModerationSettingModal: vi.fn(),
setShowExternalDataToolModal: vi.fn(),
setShowPricingModal: mockSetShowPricingModal,
setShowAnnotationFullModal: vi.fn(),
setShowModelModal: vi.fn(),
setShowExternalKnowledgeAPIModal: vi.fn(),
setShowModelLoadBalancingModal: vi.fn(),
setShowOpeningModal: vi.fn(),
setShowUpdatePluginModal: vi.fn(),
setShowEducationExpireNoticeModal: vi.fn(),
setShowTriggerEventsLimitModal: vi.fn(),
}),
}))
const mockUseProviderContext = vi.fn()
vi.mock('@/context/provider-context', () => ({
useProviderContext: () => mockUseProviderContext(),
}))
vi.mock('@/context/i18n', async () => {
const actual = await vi.importActual<typeof import('@/context/i18n')>('@/context/i18n')
return {
...actual,
useDocLink: () => (path?: string) => `https://docs.example.com${path ?? ''}`,
}
})
describe('EditCustomCollectionModal', () => {
const mockOnHide = vi.fn()
const mockOnAdd = vi.fn()
const mockOnEdit = vi.fn()
const mockOnRemove = vi.fn()
const toastNotifySpy = vi.spyOn(Toast, 'notify')
beforeEach(() => {
vi.clearAllMocks()
toastNotifySpy.mockClear()
parseParamsSchemaMock.mockResolvedValue({
parameters_schema: [],
schema_type: 'openapi',
})
mockUseProviderContext.mockReturnValue({
plan: {
type: Plan.sandbox,
},
enableBilling: false,
webappCopyrightEnabled: true,
} as ProviderContextState)
})
const renderModal = () => render(
<EditCustomCollectionModal
payload={undefined}
onHide={mockOnHide}
onAdd={mockOnAdd}
onEdit={mockOnEdit}
onRemove={mockOnRemove}
/>,
)
it('shows an error when the provider name is missing', async () => {
renderModal()
const schemaInput = screen.getByPlaceholderText('tools.createTool.schemaPlaceHolder')
fireEvent.change(schemaInput, { target: { value: '{}' } })
await waitFor(() => {
expect(parseParamsSchemaMock).toHaveBeenCalledWith('{}')
})
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()
})
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(mockOnAdd).toHaveBeenCalledWith(expect.objectContaining({
provider: 'provider',
schema: '{}',
schema_type: 'openapi',
credentials: {
auth_type: 'none',
},
labels: [],
}))
expect(toastNotifySpy).not.toHaveBeenCalled()
})
})
})

View File

@ -0,0 +1,87 @@
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 I18n from '@/context/i18n'
import { testAPIAvailable } from '@/service/tools'
import TestApi from './test-api'
vi.mock('@/service/tools', () => ({
testAPIAvailable: vi.fn(),
}))
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: '限制',
},
// eslint-disable-next-line ts/no-explicit-any
} as any],
}
const renderTestApi = () => {
const providerValue = {
locale: 'en-US',
i18n: {},
setLocaleOnClient: vi.fn(),
}
return render(
<I18n.Provider value={providerValue as any}>
<TestApi
customCollection={customCollection}
tool={tool}
onHide={vi.fn()}
/>
</I18n.Provider>,
)
}
beforeEach(() => {
vi.clearAllMocks()
})
it('renders parameters and runs the API test', 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()
})
})
})