mirror of
https://github.com/langgenius/dify.git
synced 2026-07-20 00:53:34 -04:00
test: add tests for workflow-generator to improve codecov
This commit is contained in:
committed by
GitHub
parent
e4c97139c0
commit
01419f4b4e
@@ -683,3 +683,75 @@ class TestLLMGenerator:
|
||||
"tenant_id", "flow_id", "current", "instruction", model_config_entity, "ideal"
|
||||
)
|
||||
assert "An unexpected error occurred" in result["error"]
|
||||
|
||||
@patch("core.llm_generator.llm_generator.ModelManager")
|
||||
def test_generate_workflow_instruction_suggestions_success(self, mock_model_manager):
|
||||
mock_model_instance = MagicMock()
|
||||
mock_model_manager.for_tenant.return_value.get_default_model_instance.return_value = mock_model_instance
|
||||
|
||||
mock_response = MagicMock(spec=LLMResult)
|
||||
mock_response.message.get_text_content.return_value = '["Idea 1", "Idea 2", "Idea 3", "Idea 4"]'
|
||||
mock_model_instance.invoke_llm.return_value = mock_response
|
||||
|
||||
with patch("core.llm_generator.llm_generator.LLMGenerator._build_suggestion_context", return_value="context"):
|
||||
result = LLMGenerator.generate_workflow_instruction_suggestions("tenant_id", mode="workflow", language="en", count=4)
|
||||
|
||||
assert result == ["Idea 1", "Idea 2", "Idea 3", "Idea 4"]
|
||||
|
||||
@patch("core.llm_generator.llm_generator.ModelManager")
|
||||
def test_generate_workflow_instruction_suggestions_no_model(self, mock_model_manager):
|
||||
mock_model_manager.for_tenant.return_value.get_default_model_instance.side_effect = Exception("No default model")
|
||||
|
||||
result = LLMGenerator.generate_workflow_instruction_suggestions("tenant_id", mode="workflow")
|
||||
|
||||
assert result == []
|
||||
|
||||
@patch("core.llm_generator.llm_generator.ModelManager")
|
||||
def test_generate_workflow_instruction_suggestions_invoke_error(self, mock_model_manager):
|
||||
mock_model_instance = MagicMock()
|
||||
mock_model_manager.for_tenant.return_value.get_default_model_instance.return_value = mock_model_instance
|
||||
mock_model_instance.invoke_llm.side_effect = Exception("Invoke error")
|
||||
|
||||
with patch("core.llm_generator.llm_generator.LLMGenerator._build_suggestion_context", return_value="context"):
|
||||
result = LLMGenerator.generate_workflow_instruction_suggestions("tenant_id", mode="workflow")
|
||||
|
||||
assert result == []
|
||||
|
||||
@patch("core.llm_generator.llm_generator.ModelManager")
|
||||
def test_generate_workflow_instruction_suggestions_with_chatflow(self, mock_model_manager):
|
||||
mock_model_instance = MagicMock()
|
||||
mock_model_manager.for_tenant.return_value.get_default_model_instance.return_value = mock_model_instance
|
||||
|
||||
mock_response = MagicMock(spec=LLMResult)
|
||||
mock_response.message.get_text_content.return_value = '["Idea 1", "Idea 2"]'
|
||||
mock_model_instance.invoke_llm.return_value = mock_response
|
||||
|
||||
with patch("core.llm_generator.llm_generator.LLMGenerator._build_suggestion_context", return_value="context"):
|
||||
result = LLMGenerator.generate_workflow_instruction_suggestions("tenant_id", mode="advanced-chat", count=2)
|
||||
|
||||
assert result == ["Idea 1", "Idea 2"]
|
||||
|
||||
@patch("core.llm_generator.llm_generator.db")
|
||||
@patch("core.llm_generator.llm_generator.build_tool_catalogue")
|
||||
@patch("core.llm_generator.llm_generator.format_tool_catalogue")
|
||||
def test_build_suggestion_context_success(self, mock_format, mock_build, mock_db):
|
||||
mock_db.session.scalars.return_value.all.return_value = ["kb1", "kb2"]
|
||||
mock_format.return_value = "tool1\ntool2"
|
||||
|
||||
result = LLMGenerator._build_suggestion_context("tenant_id")
|
||||
|
||||
assert "Knowledge bases:" in result
|
||||
assert "- kb1" in result
|
||||
assert "- kb2" in result
|
||||
assert "Installed tools:" in result
|
||||
assert "tool1" in result
|
||||
|
||||
@patch("core.llm_generator.llm_generator.db")
|
||||
@patch("core.llm_generator.llm_generator.build_tool_catalogue")
|
||||
def test_build_suggestion_context_errors(self, mock_build, mock_db):
|
||||
mock_db.session.scalars.side_effect = Exception("DB Error")
|
||||
mock_build.side_effect = Exception("Tool Error")
|
||||
|
||||
result = LLMGenerator._build_suggestion_context("tenant_id")
|
||||
|
||||
assert result == ""
|
||||
|
||||
@@ -280,3 +280,97 @@ describe('handleStream', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('sseGeneratorPost', () => {
|
||||
it('should call fetch and process data correctly', async () => {
|
||||
const { sseGeneratorPost } = await import('./base');
|
||||
const mockOnPlan = vi.fn()
|
||||
const mockOnResult = vi.fn()
|
||||
const mockOnCompleted = vi.fn()
|
||||
|
||||
// Create a mock stream reader
|
||||
const mockReader = {
|
||||
read: vi.fn()
|
||||
}
|
||||
|
||||
// Setup the mock reader to return two chunks then done
|
||||
const encoder = new TextEncoder()
|
||||
mockReader.read.mockResolvedValueOnce({
|
||||
done: false,
|
||||
value: encoder.encode('data: {"event":"plan","plan":"test"}\n\n')
|
||||
}).mockResolvedValueOnce({
|
||||
done: false,
|
||||
value: encoder.encode('data: {"event":"result","result":"test"}\n\n')
|
||||
}).mockResolvedValueOnce({
|
||||
done: true
|
||||
})
|
||||
|
||||
// Setup global fetch mock
|
||||
globalThis.fetch = vi.fn().mockResolvedValue({
|
||||
status: 200,
|
||||
body: {
|
||||
getReader: () => mockReader
|
||||
}
|
||||
})
|
||||
|
||||
sseGeneratorPost('/test-url', { test: true }, {
|
||||
onPlan: mockOnPlan,
|
||||
onResult: mockOnResult,
|
||||
onCompleted: mockOnCompleted
|
||||
})
|
||||
|
||||
// Give promises time to resolve
|
||||
await new Promise(resolve => setTimeout(resolve, 100))
|
||||
|
||||
expect(globalThis.fetch).toHaveBeenCalled()
|
||||
expect(mockOnPlan).toHaveBeenCalledWith({ event: 'plan', plan: 'test' })
|
||||
expect(mockOnResult).toHaveBeenCalledWith({ event: 'result', result: 'test' })
|
||||
expect(mockOnCompleted).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should handle partial JSON properly', async () => {
|
||||
const { sseGeneratorPost } = await import('./base');
|
||||
const mockOnPlan = vi.fn()
|
||||
|
||||
const mockReader = { read: vi.fn() }
|
||||
const encoder = new TextEncoder()
|
||||
|
||||
// Return partial JSON in first chunk, rest in second chunk
|
||||
mockReader.read.mockResolvedValueOnce({
|
||||
done: false,
|
||||
value: encoder.encode('data: {"event":"plan","plan')
|
||||
}).mockResolvedValueOnce({
|
||||
done: false,
|
||||
value: encoder.encode('":"test"}\n\n')
|
||||
}).mockResolvedValueOnce({
|
||||
done: true
|
||||
})
|
||||
|
||||
globalThis.fetch = vi.fn().mockResolvedValue({
|
||||
status: 200,
|
||||
body: { getReader: () => mockReader }
|
||||
})
|
||||
|
||||
sseGeneratorPost('/test-url', {}, { onPlan: mockOnPlan })
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 100))
|
||||
|
||||
expect(mockOnPlan).toHaveBeenCalledWith({ event: 'plan', plan: 'test' })
|
||||
})
|
||||
|
||||
it('should handle errors gracefully', async () => {
|
||||
const { sseGeneratorPost } = await import('./base');
|
||||
const mockOnError = vi.fn()
|
||||
|
||||
globalThis.fetch = vi.fn().mockResolvedValue({
|
||||
status: 500,
|
||||
json: vi.fn().mockResolvedValue({ message: 'Server error' })
|
||||
})
|
||||
|
||||
sseGeneratorPost('/test-url', {}, { onError: mockOnError })
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 100))
|
||||
|
||||
expect(mockOnError).toHaveBeenCalledWith('Server error')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// no-restricted-imports rule targets production imports, not test
|
||||
// instrumentation — mirrors sibling service specs (annotation.spec.ts etc.).
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { post } from './base'
|
||||
import { generateWorkflow } from './debug'
|
||||
import { post, get } from './base'
|
||||
import { generateWorkflow, fetchWorkflowInstructionSuggestions } from './debug'
|
||||
|
||||
// Stub the shared `post` wrapper so tests verify only what `generateWorkflow`
|
||||
// composes on top of it — URL, body, and the typed response surface.
|
||||
@@ -81,3 +81,26 @@ describe('debug service — generateWorkflow', () => {
|
||||
expect(vi.mocked(post).mock.calls[0]).toHaveLength(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('fetchWorkflowInstructionSuggestions', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('should call post on /workflow-generate/suggestions', () => {
|
||||
const body = { mode: 'workflow' as const, language: 'en', count: 4 };
|
||||
|
||||
fetchWorkflowInstructionSuggestions(body);
|
||||
|
||||
expect(post).toHaveBeenCalledWith('/workflow-generate/suggestions', { body });
|
||||
});
|
||||
|
||||
it('should forward getAbortController to post when provided', () => {
|
||||
const body = { mode: 'workflow' as const, language: 'en', count: 4 };
|
||||
const getAbortController = vi.fn();
|
||||
|
||||
fetchWorkflowInstructionSuggestions(body, { getAbortController });
|
||||
|
||||
expect(post).toHaveBeenCalledWith('/workflow-generate/suggestions', { body }, { getAbortController });
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user