mirror of
https://github.com/langgenius/dify.git
synced 2026-04-27 11:06:46 +08:00
Co-authored-by: CodingOnStar <hanxujiang@dify.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
426 lines
13 KiB
TypeScript
426 lines
13 KiB
TypeScript
import type { TryAppInfo } from '@/service/try-app'
|
|
import { renderHook } from '@testing-library/react'
|
|
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
import useGetRequirements from './use-get-requirements'
|
|
|
|
const mockUseGetTryAppFlowPreview = vi.fn()
|
|
|
|
vi.mock('@/service/use-try-app', () => ({
|
|
useGetTryAppFlowPreview: (...args: unknown[]) => mockUseGetTryAppFlowPreview(...args),
|
|
}))
|
|
|
|
vi.mock('@/config', () => ({
|
|
MARKETPLACE_API_PREFIX: 'https://marketplace.api',
|
|
}))
|
|
|
|
const createMockAppDetail = (mode: string, overrides: Partial<TryAppInfo> = {}): TryAppInfo => ({
|
|
id: 'test-app-id',
|
|
name: 'Test App',
|
|
description: 'Test Description',
|
|
mode,
|
|
site: {
|
|
title: 'Test Site Title',
|
|
icon: 'icon',
|
|
icon_type: 'emoji',
|
|
icon_background: '#FFFFFF',
|
|
icon_url: '',
|
|
},
|
|
model_config: {
|
|
model: {
|
|
provider: 'langgenius/openai/openai',
|
|
name: 'gpt-4',
|
|
mode: 'chat',
|
|
},
|
|
dataset_configs: {
|
|
datasets: {
|
|
datasets: [],
|
|
},
|
|
},
|
|
agent_mode: {
|
|
tools: [],
|
|
},
|
|
user_input_form: [],
|
|
},
|
|
...overrides,
|
|
} as unknown as TryAppInfo)
|
|
|
|
describe('useGetRequirements', () => {
|
|
afterEach(() => {
|
|
vi.clearAllMocks()
|
|
})
|
|
|
|
describe('basic app modes (chat, completion, agent-chat)', () => {
|
|
it('returns model provider for chat mode', () => {
|
|
mockUseGetTryAppFlowPreview.mockReturnValue({ data: null })
|
|
|
|
const appDetail = createMockAppDetail('chat')
|
|
const { result } = renderHook(() =>
|
|
useGetRequirements({ appDetail, appId: 'test-app-id' }),
|
|
)
|
|
|
|
expect(result.current.requirements).toHaveLength(1)
|
|
expect(result.current.requirements[0].name).toBe('openai')
|
|
expect(result.current.requirements[0].iconUrl).toBe('https://marketplace.api/plugins/langgenius/openai/icon')
|
|
})
|
|
|
|
it('returns model provider for completion mode', () => {
|
|
mockUseGetTryAppFlowPreview.mockReturnValue({ data: null })
|
|
|
|
const appDetail = createMockAppDetail('completion', {
|
|
model_config: {
|
|
model: {
|
|
provider: 'anthropic/claude/claude',
|
|
name: 'claude-3',
|
|
mode: 'completion',
|
|
},
|
|
dataset_configs: { datasets: { datasets: [] } },
|
|
agent_mode: { tools: [] },
|
|
user_input_form: [],
|
|
},
|
|
} as unknown as Partial<TryAppInfo>)
|
|
|
|
const { result } = renderHook(() =>
|
|
useGetRequirements({ appDetail, appId: 'test-app-id' }),
|
|
)
|
|
|
|
expect(result.current.requirements).toHaveLength(1)
|
|
expect(result.current.requirements[0].name).toBe('claude')
|
|
})
|
|
|
|
it('returns model provider and tools for agent-chat mode', () => {
|
|
mockUseGetTryAppFlowPreview.mockReturnValue({ data: null })
|
|
|
|
const appDetail = createMockAppDetail('agent-chat', {
|
|
model_config: {
|
|
model: {
|
|
provider: 'langgenius/openai/openai',
|
|
name: 'gpt-4',
|
|
mode: 'chat',
|
|
},
|
|
dataset_configs: { datasets: { datasets: [] } },
|
|
agent_mode: {
|
|
tools: [
|
|
{
|
|
enabled: true,
|
|
provider_id: 'langgenius/google_search/google_search',
|
|
tool_label: 'Google Search',
|
|
},
|
|
{
|
|
enabled: true,
|
|
provider_id: 'langgenius/web_scraper/web_scraper',
|
|
tool_label: 'Web Scraper',
|
|
},
|
|
{
|
|
enabled: false,
|
|
provider_id: 'langgenius/disabled_tool/disabled_tool',
|
|
tool_label: 'Disabled Tool',
|
|
},
|
|
],
|
|
},
|
|
user_input_form: [],
|
|
},
|
|
} as unknown as Partial<TryAppInfo>)
|
|
|
|
const { result } = renderHook(() =>
|
|
useGetRequirements({ appDetail, appId: 'test-app-id' }),
|
|
)
|
|
|
|
expect(result.current.requirements).toHaveLength(3)
|
|
expect(result.current.requirements.map(r => r.name)).toContain('openai')
|
|
expect(result.current.requirements.map(r => r.name)).toContain('Google Search')
|
|
expect(result.current.requirements.map(r => r.name)).toContain('Web Scraper')
|
|
expect(result.current.requirements.map(r => r.name)).not.toContain('Disabled Tool')
|
|
})
|
|
|
|
it('filters out disabled tools in agent mode', () => {
|
|
mockUseGetTryAppFlowPreview.mockReturnValue({ data: null })
|
|
|
|
const appDetail = createMockAppDetail('agent-chat', {
|
|
model_config: {
|
|
model: {
|
|
provider: 'langgenius/openai/openai',
|
|
name: 'gpt-4',
|
|
mode: 'chat',
|
|
},
|
|
dataset_configs: { datasets: { datasets: [] } },
|
|
agent_mode: {
|
|
tools: [
|
|
{
|
|
enabled: false,
|
|
provider_id: 'langgenius/tool1/tool1',
|
|
tool_label: 'Tool 1',
|
|
},
|
|
{
|
|
enabled: false,
|
|
provider_id: 'langgenius/tool2/tool2',
|
|
tool_label: 'Tool 2',
|
|
},
|
|
],
|
|
},
|
|
user_input_form: [],
|
|
},
|
|
} as unknown as Partial<TryAppInfo>)
|
|
|
|
const { result } = renderHook(() =>
|
|
useGetRequirements({ appDetail, appId: 'test-app-id' }),
|
|
)
|
|
|
|
// Only model provider should be included, no disabled tools
|
|
expect(result.current.requirements).toHaveLength(1)
|
|
expect(result.current.requirements[0].name).toBe('openai')
|
|
})
|
|
})
|
|
|
|
describe('advanced app modes (workflow, advanced-chat)', () => {
|
|
it('returns requirements from flow data for workflow mode', () => {
|
|
mockUseGetTryAppFlowPreview.mockReturnValue({
|
|
data: {
|
|
graph: {
|
|
nodes: [
|
|
{
|
|
data: {
|
|
type: 'llm',
|
|
model: {
|
|
provider: 'langgenius/openai/openai',
|
|
name: 'gpt-4',
|
|
},
|
|
},
|
|
},
|
|
{
|
|
data: {
|
|
type: 'tool',
|
|
provider_id: 'langgenius/google/google',
|
|
tool_label: 'Google Tool',
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
})
|
|
|
|
const appDetail = createMockAppDetail('workflow')
|
|
const { result } = renderHook(() =>
|
|
useGetRequirements({ appDetail, appId: 'test-app-id' }),
|
|
)
|
|
|
|
expect(result.current.requirements).toHaveLength(2)
|
|
expect(result.current.requirements.map(r => r.name)).toContain('gpt-4')
|
|
expect(result.current.requirements.map(r => r.name)).toContain('Google Tool')
|
|
})
|
|
|
|
it('returns requirements from flow data for advanced-chat mode', () => {
|
|
mockUseGetTryAppFlowPreview.mockReturnValue({
|
|
data: {
|
|
graph: {
|
|
nodes: [
|
|
{
|
|
data: {
|
|
type: 'llm',
|
|
model: {
|
|
provider: 'anthropic/claude/claude',
|
|
name: 'claude-3-opus',
|
|
},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
})
|
|
|
|
const appDetail = createMockAppDetail('advanced-chat')
|
|
const { result } = renderHook(() =>
|
|
useGetRequirements({ appDetail, appId: 'test-app-id' }),
|
|
)
|
|
|
|
expect(result.current.requirements).toHaveLength(1)
|
|
expect(result.current.requirements[0].name).toBe('claude-3-opus')
|
|
})
|
|
|
|
it('returns empty requirements when flow data has no nodes', () => {
|
|
mockUseGetTryAppFlowPreview.mockReturnValue({
|
|
data: {
|
|
graph: {
|
|
nodes: [],
|
|
},
|
|
},
|
|
})
|
|
|
|
const appDetail = createMockAppDetail('workflow')
|
|
const { result } = renderHook(() =>
|
|
useGetRequirements({ appDetail, appId: 'test-app-id' }),
|
|
)
|
|
|
|
expect(result.current.requirements).toHaveLength(0)
|
|
})
|
|
|
|
it('returns empty requirements when flow data is null', () => {
|
|
mockUseGetTryAppFlowPreview.mockReturnValue({
|
|
data: null,
|
|
})
|
|
|
|
const appDetail = createMockAppDetail('workflow')
|
|
const { result } = renderHook(() =>
|
|
useGetRequirements({ appDetail, appId: 'test-app-id' }),
|
|
)
|
|
|
|
expect(result.current.requirements).toHaveLength(0)
|
|
})
|
|
|
|
it('extracts multiple LLM nodes from flow data', () => {
|
|
mockUseGetTryAppFlowPreview.mockReturnValue({
|
|
data: {
|
|
graph: {
|
|
nodes: [
|
|
{
|
|
data: {
|
|
type: 'llm',
|
|
model: {
|
|
provider: 'langgenius/openai/openai',
|
|
name: 'gpt-4',
|
|
},
|
|
},
|
|
},
|
|
{
|
|
data: {
|
|
type: 'llm',
|
|
model: {
|
|
provider: 'anthropic/claude/claude',
|
|
name: 'claude-3',
|
|
},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
})
|
|
|
|
const appDetail = createMockAppDetail('workflow')
|
|
const { result } = renderHook(() =>
|
|
useGetRequirements({ appDetail, appId: 'test-app-id' }),
|
|
)
|
|
|
|
expect(result.current.requirements).toHaveLength(2)
|
|
expect(result.current.requirements.map(r => r.name)).toContain('gpt-4')
|
|
expect(result.current.requirements.map(r => r.name)).toContain('claude-3')
|
|
})
|
|
|
|
it('extracts multiple tool nodes from flow data', () => {
|
|
mockUseGetTryAppFlowPreview.mockReturnValue({
|
|
data: {
|
|
graph: {
|
|
nodes: [
|
|
{
|
|
data: {
|
|
type: 'tool',
|
|
provider_id: 'langgenius/tool1/tool1',
|
|
tool_label: 'Tool 1',
|
|
},
|
|
},
|
|
{
|
|
data: {
|
|
type: 'tool',
|
|
provider_id: 'langgenius/tool2/tool2',
|
|
tool_label: 'Tool 2',
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
})
|
|
|
|
const appDetail = createMockAppDetail('workflow')
|
|
const { result } = renderHook(() =>
|
|
useGetRequirements({ appDetail, appId: 'test-app-id' }),
|
|
)
|
|
|
|
expect(result.current.requirements).toHaveLength(2)
|
|
expect(result.current.requirements.map(r => r.name)).toContain('Tool 1')
|
|
expect(result.current.requirements.map(r => r.name)).toContain('Tool 2')
|
|
})
|
|
})
|
|
|
|
describe('deduplication', () => {
|
|
it('removes duplicate requirements by name', () => {
|
|
mockUseGetTryAppFlowPreview.mockReturnValue({
|
|
data: {
|
|
graph: {
|
|
nodes: [
|
|
{
|
|
data: {
|
|
type: 'llm',
|
|
model: {
|
|
provider: 'langgenius/openai/openai',
|
|
name: 'gpt-4',
|
|
},
|
|
},
|
|
},
|
|
{
|
|
data: {
|
|
type: 'llm',
|
|
model: {
|
|
provider: 'langgenius/openai/openai',
|
|
name: 'gpt-4',
|
|
},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
})
|
|
|
|
const appDetail = createMockAppDetail('workflow')
|
|
const { result } = renderHook(() =>
|
|
useGetRequirements({ appDetail, appId: 'test-app-id' }),
|
|
)
|
|
|
|
expect(result.current.requirements).toHaveLength(1)
|
|
expect(result.current.requirements[0].name).toBe('gpt-4')
|
|
})
|
|
})
|
|
|
|
describe('icon URL generation', () => {
|
|
it('generates correct icon URL for model providers', () => {
|
|
mockUseGetTryAppFlowPreview.mockReturnValue({ data: null })
|
|
|
|
const appDetail = createMockAppDetail('chat', {
|
|
model_config: {
|
|
model: {
|
|
provider: 'org/plugin/model',
|
|
name: 'model-name',
|
|
mode: 'chat',
|
|
},
|
|
dataset_configs: { datasets: { datasets: [] } },
|
|
agent_mode: { tools: [] },
|
|
user_input_form: [],
|
|
},
|
|
} as unknown as Partial<TryAppInfo>)
|
|
|
|
const { result } = renderHook(() =>
|
|
useGetRequirements({ appDetail, appId: 'test-app-id' }),
|
|
)
|
|
|
|
expect(result.current.requirements[0].iconUrl).toBe('https://marketplace.api/plugins/org/plugin/icon')
|
|
})
|
|
})
|
|
|
|
describe('hook calls', () => {
|
|
it('calls useGetTryAppFlowPreview with correct parameters for basic apps', () => {
|
|
mockUseGetTryAppFlowPreview.mockReturnValue({ data: null })
|
|
|
|
const appDetail = createMockAppDetail('chat')
|
|
renderHook(() => useGetRequirements({ appDetail, appId: 'test-app-id' }))
|
|
|
|
expect(mockUseGetTryAppFlowPreview).toHaveBeenCalledWith('test-app-id', true)
|
|
})
|
|
|
|
it('calls useGetTryAppFlowPreview with correct parameters for advanced apps', () => {
|
|
mockUseGetTryAppFlowPreview.mockReturnValue({ data: null })
|
|
|
|
const appDetail = createMockAppDetail('workflow')
|
|
renderHook(() => useGetRequirements({ appDetail, appId: 'test-app-id' }))
|
|
|
|
expect(mockUseGetTryAppFlowPreview).toHaveBeenCalledWith('test-app-id', false)
|
|
})
|
|
})
|
|
})
|