mirror of
https://github.com/langgenius/dify.git
synced 2026-03-19 21:57:33 +08:00
Signed-off-by: yyh <yuanyouhuilyz@gmail.com> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: CodingOnStar <hanxujiang@dify.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Coding On Star <447357187@qq.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: -LAN- <laipz8200@outlook.com> Co-authored-by: statxc <tyleradams93226@gmail.com>
234 lines
7.3 KiB
TypeScript
234 lines
7.3 KiB
TypeScript
import type { KnowledgeBaseNodeType } from './types'
|
|
import type { ModelItem } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
|
import type { CommonNodeType } from '@/app/components/workflow/types'
|
|
import { render, screen } from '@testing-library/react'
|
|
import {
|
|
ConfigurationMethodEnum,
|
|
ModelStatusEnum,
|
|
ModelTypeEnum,
|
|
} from '@/app/components/header/account-setting/model-provider-page/declarations'
|
|
import { BlockEnum } from '@/app/components/workflow/types'
|
|
import Node from './node'
|
|
import {
|
|
ChunkStructureEnum,
|
|
IndexMethodEnum,
|
|
RetrievalSearchMethodEnum,
|
|
} from './types'
|
|
|
|
const mockUseModelList = vi.hoisted(() => vi.fn())
|
|
const mockUseSettingsDisplay = vi.hoisted(() => vi.fn())
|
|
const mockUseEmbeddingModelStatus = vi.hoisted(() => vi.fn())
|
|
|
|
vi.mock('@tanstack/react-query', async (importOriginal) => {
|
|
const actual = await importOriginal<typeof import('@tanstack/react-query')>()
|
|
return {
|
|
...actual,
|
|
useQuery: () => ({ data: undefined }),
|
|
}
|
|
})
|
|
|
|
vi.mock('@/app/components/header/account-setting/model-provider-page/hooks', async (importOriginal) => {
|
|
const actual = await importOriginal<typeof import('@/app/components/header/account-setting/model-provider-page/hooks')>()
|
|
return {
|
|
...actual,
|
|
useLanguage: () => 'en_US',
|
|
useModelList: mockUseModelList,
|
|
}
|
|
})
|
|
|
|
vi.mock('./hooks/use-settings-display', () => ({
|
|
useSettingsDisplay: mockUseSettingsDisplay,
|
|
}))
|
|
|
|
vi.mock('./hooks/use-embedding-model-status', () => ({
|
|
useEmbeddingModelStatus: mockUseEmbeddingModelStatus,
|
|
}))
|
|
|
|
const createModelItem = (overrides: Partial<ModelItem> = {}): ModelItem => ({
|
|
model: 'text-embedding-3-large',
|
|
label: { en_US: 'Text Embedding 3 Large', zh_Hans: 'Text Embedding 3 Large' },
|
|
model_type: ModelTypeEnum.textEmbedding,
|
|
fetch_from: ConfigurationMethodEnum.predefinedModel,
|
|
status: ModelStatusEnum.active,
|
|
model_properties: {},
|
|
load_balancing_enabled: false,
|
|
...overrides,
|
|
})
|
|
|
|
const createNodeData = (overrides: Partial<CommonNodeType<KnowledgeBaseNodeType>> = {}): CommonNodeType<KnowledgeBaseNodeType> => ({
|
|
title: 'Knowledge Base',
|
|
desc: '',
|
|
type: BlockEnum.KnowledgeBase,
|
|
index_chunk_variable_selector: ['result'],
|
|
chunk_structure: ChunkStructureEnum.general,
|
|
indexing_technique: IndexMethodEnum.QUALIFIED,
|
|
embedding_model: 'text-embedding-3-large',
|
|
embedding_model_provider: 'openai',
|
|
keyword_number: 10,
|
|
retrieval_model: {
|
|
top_k: 3,
|
|
score_threshold_enabled: false,
|
|
score_threshold: 0.5,
|
|
search_method: RetrievalSearchMethodEnum.semantic,
|
|
},
|
|
...overrides,
|
|
})
|
|
|
|
describe('KnowledgeBaseNode', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
mockUseModelList.mockReturnValue({ data: [] })
|
|
mockUseSettingsDisplay.mockReturnValue({
|
|
[IndexMethodEnum.QUALIFIED]: 'High Quality',
|
|
[RetrievalSearchMethodEnum.semantic]: 'Vector Search',
|
|
})
|
|
mockUseEmbeddingModelStatus.mockReturnValue({
|
|
providerMeta: undefined,
|
|
modelProvider: undefined,
|
|
currentModel: createModelItem(),
|
|
status: 'active',
|
|
})
|
|
})
|
|
|
|
// Embedding model row should mirror the selector status labels.
|
|
describe('Embedding Model Status', () => {
|
|
it('should render active embedding model label when the model is available', () => {
|
|
render(<Node id="knowledge-base-1" data={createNodeData()} />)
|
|
|
|
expect(screen.getByText('Text Embedding 3 Large')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should render configure required when embedding model status requires configuration', () => {
|
|
mockUseEmbeddingModelStatus.mockReturnValue({
|
|
providerMeta: undefined,
|
|
modelProvider: undefined,
|
|
currentModel: createModelItem({ status: ModelStatusEnum.noConfigure }),
|
|
status: 'configure-required',
|
|
})
|
|
|
|
render(<Node id="knowledge-base-1" data={createNodeData()} />)
|
|
|
|
expect(screen.getByText('common.modelProvider.selector.configureRequired')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should render disabled when embedding model status is disabled', () => {
|
|
mockUseEmbeddingModelStatus.mockReturnValue({
|
|
providerMeta: undefined,
|
|
modelProvider: undefined,
|
|
currentModel: createModelItem({ status: ModelStatusEnum.disabled }),
|
|
status: 'disabled',
|
|
})
|
|
|
|
render(<Node id="knowledge-base-1" data={createNodeData()} />)
|
|
|
|
expect(screen.getByText('common.modelProvider.selector.disabled')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should render incompatible when embedding model status is incompatible', () => {
|
|
mockUseEmbeddingModelStatus.mockReturnValue({
|
|
providerMeta: undefined,
|
|
modelProvider: undefined,
|
|
currentModel: undefined,
|
|
status: 'incompatible',
|
|
})
|
|
|
|
render(<Node id="knowledge-base-1" data={createNodeData()} />)
|
|
|
|
expect(screen.getByText('common.modelProvider.selector.incompatible')).toBeInTheDocument()
|
|
})
|
|
|
|
it('should render configure model prompt when no embedding model is selected', () => {
|
|
mockUseEmbeddingModelStatus.mockReturnValue({
|
|
providerMeta: undefined,
|
|
modelProvider: undefined,
|
|
currentModel: undefined,
|
|
status: 'empty',
|
|
})
|
|
|
|
render(
|
|
<Node
|
|
id="knowledge-base-1"
|
|
data={createNodeData({
|
|
embedding_model: undefined,
|
|
embedding_model_provider: undefined,
|
|
})}
|
|
/>,
|
|
)
|
|
|
|
expect(screen.getByText('plugin.detailPanel.configureModel')).toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
describe('Validation warnings', () => {
|
|
it('should render a warning banner when chunk structure is missing', () => {
|
|
render(
|
|
<Node
|
|
id="knowledge-base-1"
|
|
data={createNodeData({
|
|
chunk_structure: undefined,
|
|
})}
|
|
/>,
|
|
)
|
|
|
|
expect(screen.getByText(/chunkIsRequired/i)).toBeInTheDocument()
|
|
})
|
|
|
|
it('should render a warning value for the chunks input row when no chunk variable is selected', () => {
|
|
render(
|
|
<Node
|
|
id="knowledge-base-1"
|
|
data={createNodeData({
|
|
index_chunk_variable_selector: [],
|
|
})}
|
|
/>,
|
|
)
|
|
|
|
expect(screen.getByText(/chunksVariableIsRequired/i)).toBeInTheDocument()
|
|
})
|
|
|
|
it('should render a warning value for retrieval settings when reranking is incomplete', () => {
|
|
mockUseModelList.mockImplementation((modelType: ModelTypeEnum) => {
|
|
if (modelType === ModelTypeEnum.textEmbedding) {
|
|
return {
|
|
data: [{
|
|
provider: 'openai',
|
|
models: [createModelItem()],
|
|
}],
|
|
}
|
|
}
|
|
return { data: [] }
|
|
})
|
|
|
|
render(
|
|
<Node
|
|
id="knowledge-base-1"
|
|
data={createNodeData({
|
|
retrieval_model: {
|
|
top_k: 3,
|
|
score_threshold_enabled: false,
|
|
score_threshold: 0.5,
|
|
search_method: RetrievalSearchMethodEnum.semantic,
|
|
reranking_enable: true,
|
|
},
|
|
})}
|
|
/>,
|
|
)
|
|
|
|
expect(screen.getByText(/rerankingModelIsRequired/i)).toBeInTheDocument()
|
|
})
|
|
|
|
it('should hide the embedding model row when the index method is not qualified', () => {
|
|
render(
|
|
<Node
|
|
id="knowledge-base-1"
|
|
data={createNodeData({
|
|
indexing_technique: IndexMethodEnum.ECONOMICAL,
|
|
})}
|
|
/>,
|
|
)
|
|
|
|
expect(screen.queryByText('Text Embedding 3 Large')).not.toBeInTheDocument()
|
|
})
|
|
})
|
|
})
|