mirror of
https://github.com/langgenius/dify.git
synced 2026-07-16 01:48:40 +08:00
242 lines
7.5 KiB
TypeScript
242 lines
7.5 KiB
TypeScript
import type { AppModeEnum } from '@/types/app'
|
|
// service/base is the dependency we're mocking in this test; the
|
|
// no-restricted-imports rule targets production imports, not test
|
|
// instrumentation — mirrors sibling service specs (annotation.spec.ts etc.).
|
|
// oxlint-disable-next-line no-restricted-imports
|
|
import { get, post, ssePost } from './base'
|
|
import { consoleClient, streamWorkflowGeneration } from './client'
|
|
import {
|
|
fetchConversationMessages,
|
|
fetchPromptTemplate,
|
|
fetchSuggestedQuestions,
|
|
fetchTextGenerationMessage,
|
|
generateBasicAppFirstTimeRule,
|
|
generateRule,
|
|
sendCompletionMessage,
|
|
stopChatMessageResponding,
|
|
} from './debug'
|
|
import {
|
|
fetchWorkflowInstructionSuggestions,
|
|
generateWorkflow,
|
|
generateWorkflowStream,
|
|
} from './workflow-generator'
|
|
|
|
// Stub the shared `post` wrapper so tests verify only what `generateWorkflow`
|
|
// composes on top of it — URL, body, and the typed response surface.
|
|
vi.mock('./base', () => ({
|
|
post: vi.fn(),
|
|
get: vi.fn(),
|
|
ssePost: vi.fn(),
|
|
}))
|
|
|
|
vi.mock('./client', () => ({
|
|
streamWorkflowGeneration: vi.fn(),
|
|
consoleClient: {
|
|
workflowGenerate: {
|
|
post: vi.fn(),
|
|
suggestions: {
|
|
post: vi.fn(),
|
|
},
|
|
},
|
|
},
|
|
}))
|
|
|
|
describe('debug service — generateWorkflow', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
})
|
|
|
|
// The new endpoint lives at /workflow-generate; the controller mirrors
|
|
// /rule-generate so the body must flow through unchanged.
|
|
it('should call the generated workflow contract with the body verbatim', () => {
|
|
const body = {
|
|
mode: 'workflow' as const,
|
|
instruction: 'Summarize a URL',
|
|
ideal_output: 'A 3-sentence summary.',
|
|
model_config: {
|
|
provider: 'openai',
|
|
name: 'gpt-4o',
|
|
mode: 'chat' as const,
|
|
completion_params: {},
|
|
},
|
|
}
|
|
|
|
generateWorkflow(body)
|
|
|
|
expect(consoleClient.workflowGenerate.post).toHaveBeenCalledWith(
|
|
{ body },
|
|
{ signal: expect.any(AbortSignal) },
|
|
)
|
|
})
|
|
|
|
// The optional fields must still POST cleanly — `ideal_output` defaulting
|
|
// server-side requires the helper to forward the body as-is, not augment it.
|
|
it('should pass the body through even when ideal_output is omitted', () => {
|
|
const body = {
|
|
mode: 'advanced-chat' as const,
|
|
instruction: 'Friendly support bot',
|
|
model_config: { provider: 'openai', name: 'gpt-4o', mode: 'chat' as const },
|
|
}
|
|
|
|
generateWorkflow(body)
|
|
|
|
expect(consoleClient.workflowGenerate.post).toHaveBeenCalledWith(
|
|
{ body },
|
|
{ signal: expect.any(AbortSignal) },
|
|
)
|
|
})
|
|
|
|
it('should expose the controller whose signal is passed to the generated client', () => {
|
|
const body = {
|
|
mode: 'workflow' as const,
|
|
instruction: 'Long-running generation',
|
|
model_config: { provider: 'openai', name: 'gpt-4o', mode: 'chat' as const },
|
|
}
|
|
const getAbortController = vi.fn()
|
|
|
|
generateWorkflow(body, { getAbortController })
|
|
|
|
const controller = getAbortController.mock.calls[0]![0]
|
|
expect(consoleClient.workflowGenerate.post).toHaveBeenCalledWith(
|
|
{ body },
|
|
{ signal: controller.signal },
|
|
)
|
|
})
|
|
|
|
it('should create an internal abort signal when no callback is provided', () => {
|
|
const body = {
|
|
mode: 'workflow' as const,
|
|
instruction: 'Plain call',
|
|
model_config: { provider: 'openai', name: 'gpt-4o', mode: 'chat' as const },
|
|
}
|
|
|
|
generateWorkflow(body)
|
|
|
|
expect(consoleClient.workflowGenerate.post).toHaveBeenCalledWith(
|
|
{ body },
|
|
{ signal: expect.any(AbortSignal) },
|
|
)
|
|
})
|
|
|
|
describe('other endpoints', () => {
|
|
it('stopChatMessageResponding', async () => {
|
|
await stopChatMessageResponding('app-1', 'task-1')
|
|
expect(post).toHaveBeenCalledWith('apps/app-1/chat-messages/task-1/stop')
|
|
})
|
|
|
|
it('sendCompletionMessage', async () => {
|
|
const callbacks = {
|
|
onData: vi.fn(),
|
|
onCompleted: vi.fn(),
|
|
onError: vi.fn(),
|
|
onMessageReplace: vi.fn(),
|
|
}
|
|
await sendCompletionMessage('app-1', { text: 'hello' }, callbacks)
|
|
expect(ssePost).toHaveBeenCalledWith(
|
|
'apps/app-1/completion-messages',
|
|
{
|
|
body: { text: 'hello', response_mode: 'streaming' },
|
|
},
|
|
callbacks,
|
|
)
|
|
})
|
|
|
|
it('fetchSuggestedQuestions', async () => {
|
|
const getAbortController = vi.fn()
|
|
await fetchSuggestedQuestions('app-1', 'msg-1', getAbortController)
|
|
expect(get).toHaveBeenCalledWith(
|
|
'apps/app-1/chat-messages/msg-1/suggested-questions',
|
|
{},
|
|
{ getAbortController },
|
|
)
|
|
})
|
|
|
|
it('fetchConversationMessages', async () => {
|
|
const getAbortController = vi.fn()
|
|
await fetchConversationMessages('app-1', 'conv-1', getAbortController)
|
|
expect(get).toHaveBeenCalledWith(
|
|
'apps/app-1/chat-messages',
|
|
{ params: { conversation_id: 'conv-1' } },
|
|
{ getAbortController },
|
|
)
|
|
})
|
|
|
|
it('generateBasicAppFirstTimeRule', async () => {
|
|
await generateBasicAppFirstTimeRule({ mode: 'chat' })
|
|
expect(post).toHaveBeenCalledWith('/rule-generate', { body: { mode: 'chat' } })
|
|
})
|
|
|
|
it('generateRule', async () => {
|
|
await generateRule({ mode: 'chat' })
|
|
expect(post).toHaveBeenCalledWith('/instruction-generate', { body: { mode: 'chat' } })
|
|
})
|
|
|
|
it('generateWorkflowStream', async () => {
|
|
const body = {
|
|
mode: 'workflow' as const,
|
|
instruction: 'test',
|
|
model_config: { provider: 'test', name: 'test', mode: 'chat' as const },
|
|
}
|
|
const callbacks = {
|
|
onPlan: vi.fn(),
|
|
onResult: vi.fn(),
|
|
onError: vi.fn(),
|
|
onCompleted: vi.fn(),
|
|
getAbortController: vi.fn(),
|
|
}
|
|
|
|
vi.mocked(streamWorkflowGeneration).mockImplementation((_url, _body, options) => {
|
|
options?.onPlan?.({ title: 'plan' })
|
|
options?.onResult?.({ graph: { nodes: [], edges: [], viewport: { x: 0, y: 0, zoom: 1 } } })
|
|
return Promise.resolve()
|
|
})
|
|
|
|
await generateWorkflowStream(body, callbacks)
|
|
|
|
expect(streamWorkflowGeneration).toHaveBeenCalled()
|
|
expect(callbacks.onPlan).toHaveBeenCalled()
|
|
expect(callbacks.onResult).toHaveBeenCalled()
|
|
})
|
|
|
|
it('fetchWorkflowInstructionSuggestions without getAbortController', async () => {
|
|
await fetchWorkflowInstructionSuggestions({ mode: 'workflow' })
|
|
expect(consoleClient.workflowGenerate.suggestions.post).toHaveBeenCalledWith(
|
|
{ body: { mode: 'workflow' } },
|
|
{ signal: expect.any(AbortSignal) },
|
|
)
|
|
})
|
|
|
|
it('fetchWorkflowInstructionSuggestions with getAbortController', async () => {
|
|
const getAbortController = vi.fn()
|
|
await fetchWorkflowInstructionSuggestions({ mode: 'workflow' }, { getAbortController })
|
|
const controller = getAbortController.mock.calls[0]![0]
|
|
expect(consoleClient.workflowGenerate.suggestions.post).toHaveBeenCalledWith(
|
|
{ body: { mode: 'workflow' } },
|
|
{ signal: controller.signal },
|
|
)
|
|
})
|
|
|
|
it('fetchPromptTemplate', async () => {
|
|
await fetchPromptTemplate({
|
|
appMode: 'chat' as AppModeEnum,
|
|
mode: 'chat',
|
|
modelName: 'gpt-4',
|
|
hasSetDataSet: true,
|
|
})
|
|
expect(get).toHaveBeenCalledWith('/app/prompt-templates', {
|
|
params: {
|
|
app_mode: 'chat',
|
|
model_mode: 'chat',
|
|
model_name: 'gpt-4',
|
|
has_context: true,
|
|
},
|
|
})
|
|
})
|
|
|
|
it('fetchTextGenerationMessage', async () => {
|
|
await fetchTextGenerationMessage({ appId: 'app-1', messageId: 'msg-1' })
|
|
expect(get).toHaveBeenCalledWith('/apps/app-1/messages/msg-1')
|
|
})
|
|
})
|
|
})
|