dify/web/app/components/rag-pipeline/hooks/__tests__/use-pipeline-config.spec.ts

276 lines
8.8 KiB
TypeScript

import { renderHook } from '@testing-library/react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { usePipelineConfig } from '../use-pipeline-config'
const mockUseStore = vi.fn()
const mockWorkflowStoreGetState = vi.fn()
vi.mock('@/app/components/workflow/store', () => ({
useStore: (selector: (state: Record<string, unknown>) => unknown) => mockUseStore(selector),
useWorkflowStore: () => ({
getState: mockWorkflowStoreGetState,
}),
}))
const mockUseWorkflowConfig = vi.fn()
vi.mock('@/service/use-workflow', () => ({
useWorkflowConfig: (url: string, callback: (data: unknown) => void) =>
mockUseWorkflowConfig(url, callback),
}))
const mockUseDataSourceList = vi.fn()
vi.mock('@/service/use-pipeline', () => ({
useDataSourceList: (enabled: boolean, callback: (data: unknown) => void) =>
mockUseDataSourceList(enabled, callback),
}))
vi.mock('@/utils/var', () => ({
basePath: '/base',
}))
describe('usePipelineConfig', () => {
const mockSetNodesDefaultConfigs = vi.fn()
const mockSetPublishedAt = vi.fn()
const mockSetDataSourceList = vi.fn()
const mockSetFileUploadConfig = vi.fn()
beforeEach(() => {
vi.clearAllMocks()
mockUseStore.mockImplementation((selector: (state: Record<string, unknown>) => unknown) => {
const state = { pipelineId: 'test-pipeline-id' }
return selector(state)
})
mockWorkflowStoreGetState.mockReturnValue({
setNodesDefaultConfigs: mockSetNodesDefaultConfigs,
setPublishedAt: mockSetPublishedAt,
setDataSourceList: mockSetDataSourceList,
setFileUploadConfig: mockSetFileUploadConfig,
})
})
afterEach(() => {
vi.clearAllMocks()
})
describe('hook initialization', () => {
it('should call useWorkflowConfig with correct URL for nodes default configs', () => {
renderHook(() => usePipelineConfig())
expect(mockUseWorkflowConfig).toHaveBeenCalledWith(
'/rag/pipelines/test-pipeline-id/workflows/default-workflow-block-configs',
expect.any(Function),
)
})
it('should call useWorkflowConfig with correct URL for published workflow', () => {
renderHook(() => usePipelineConfig())
expect(mockUseWorkflowConfig).toHaveBeenCalledWith(
'/rag/pipelines/test-pipeline-id/workflows/publish',
expect.any(Function),
)
})
it('should call useWorkflowConfig with correct URL for file upload config', () => {
renderHook(() => usePipelineConfig())
expect(mockUseWorkflowConfig).toHaveBeenCalledWith('/files/upload', expect.any(Function))
})
it('should call useDataSourceList when pipelineId exists', () => {
renderHook(() => usePipelineConfig())
expect(mockUseDataSourceList).toHaveBeenCalledWith(true, expect.any(Function))
})
it('should call useDataSourceList with false when pipelineId is missing', () => {
mockUseStore.mockImplementation((selector: (state: Record<string, unknown>) => unknown) => {
const state = { pipelineId: undefined }
return selector(state)
})
renderHook(() => usePipelineConfig())
expect(mockUseDataSourceList).toHaveBeenCalledWith(false, expect.any(Function))
})
it('should use empty URL when pipelineId is missing for nodes configs', () => {
mockUseStore.mockImplementation((selector: (state: Record<string, unknown>) => unknown) => {
const state = { pipelineId: undefined }
return selector(state)
})
renderHook(() => usePipelineConfig())
expect(mockUseWorkflowConfig).toHaveBeenCalledWith('', expect.any(Function))
})
})
describe('handleUpdateNodesDefaultConfigs', () => {
it('should handle array format configs', () => {
let capturedCallback: ((data: unknown) => void) | undefined
mockUseWorkflowConfig.mockImplementation((url: string, callback: (data: unknown) => void) => {
if (url.includes('default-workflow-block-configs')) {
capturedCallback = callback
}
})
renderHook(() => usePipelineConfig())
const arrayConfigs = [
{ type: 'llm', config: { model: 'gpt-4' } },
{ type: 'code', config: { language: 'python' } },
]
capturedCallback?.(arrayConfigs)
expect(mockSetNodesDefaultConfigs).toHaveBeenCalledWith({
llm: { model: 'gpt-4' },
code: { language: 'python' },
})
})
it('should handle object format configs', () => {
let capturedCallback: ((data: unknown) => void) | undefined
mockUseWorkflowConfig.mockImplementation((url: string, callback: (data: unknown) => void) => {
if (url.includes('default-workflow-block-configs')) {
capturedCallback = callback
}
})
renderHook(() => usePipelineConfig())
const objectConfigs = {
llm: { model: 'gpt-4' },
code: { language: 'python' },
}
capturedCallback?.(objectConfigs)
expect(mockSetNodesDefaultConfigs).toHaveBeenCalledWith(objectConfigs)
})
})
describe('handleUpdatePublishedAt', () => {
it('should set published at from workflow response', () => {
let capturedCallback: ((data: unknown) => void) | undefined
mockUseWorkflowConfig.mockImplementation((url: string, callback: (data: unknown) => void) => {
if (url.includes('/publish')) {
capturedCallback = callback
}
})
renderHook(() => usePipelineConfig())
capturedCallback?.({ created_at: '2024-01-01T00:00:00Z' })
expect(mockSetPublishedAt).toHaveBeenCalledWith('2024-01-01T00:00:00Z')
})
it('should reset published at when workflow response is empty', () => {
let capturedCallback: ((data: unknown) => void) | undefined
mockUseWorkflowConfig.mockImplementation((url: string, callback: (data: unknown) => void) => {
if (url.includes('/publish')) {
capturedCallback = callback
}
})
renderHook(() => usePipelineConfig())
capturedCallback?.(undefined)
expect(mockSetPublishedAt).toHaveBeenCalledWith(0)
})
})
describe('handleUpdateDataSourceList', () => {
it('should set data source list', () => {
let capturedCallback: ((data: unknown) => void) | undefined
mockUseDataSourceList.mockImplementation(
(_enabled: boolean, callback: (data: unknown) => void) => {
capturedCallback = callback
},
)
renderHook(() => usePipelineConfig())
const dataSourceList = [{ declaration: { identity: { icon: '/icon.png' } } }]
capturedCallback?.(dataSourceList)
expect(mockSetDataSourceList).toHaveBeenCalled()
})
it('should prepend basePath to icon if not included', () => {
let capturedCallback: ((data: unknown) => void) | undefined
mockUseDataSourceList.mockImplementation(
(_enabled: boolean, callback: (data: unknown) => void) => {
capturedCallback = callback
},
)
renderHook(() => usePipelineConfig())
const dataSourceList = [{ declaration: { identity: { icon: '/icon.png' } } }]
capturedCallback?.(dataSourceList)
expect(dataSourceList[0]!.declaration.identity.icon).toBe('/base/icon.png')
})
it('should not modify icon if it already includes basePath', () => {
let capturedCallback: ((data: unknown) => void) | undefined
mockUseDataSourceList.mockImplementation(
(_enabled: boolean, callback: (data: unknown) => void) => {
capturedCallback = callback
},
)
renderHook(() => usePipelineConfig())
const dataSourceList = [{ declaration: { identity: { icon: '/base/icon.png' } } }]
capturedCallback?.(dataSourceList)
expect(dataSourceList[0]!.declaration.identity.icon).toBe('/base/icon.png')
})
it('should handle non-string icon', () => {
let capturedCallback: ((data: unknown) => void) | undefined
mockUseDataSourceList.mockImplementation(
(_enabled: boolean, callback: (data: unknown) => void) => {
capturedCallback = callback
},
)
renderHook(() => usePipelineConfig())
const dataSourceList = [{ declaration: { identity: { icon: { url: '/icon.png' } } } }]
capturedCallback?.(dataSourceList)
expect(dataSourceList[0]!.declaration.identity.icon).toEqual({ url: '/icon.png' })
})
})
describe('handleUpdateWorkflowFileUploadConfig', () => {
it('should set file upload config', () => {
let capturedCallback: ((data: unknown) => void) | undefined
mockUseWorkflowConfig.mockImplementation((url: string, callback: (data: unknown) => void) => {
if (url === '/files/upload') {
capturedCallback = callback
}
})
renderHook(() => usePipelineConfig())
const config = { max_file_size: 10 * 1024 * 1024 }
capturedCallback?.(config)
expect(mockSetFileUploadConfig).toHaveBeenCalledWith(config)
})
})
})