test: add tests for workflow-generator to improve codecov

This commit is contained in:
copilot-swe-agent[bot]
2026-06-30 06:59:36 +00:00
committed by GitHub
parent e4c97139c0
commit 01419f4b4e
3 changed files with 191 additions and 2 deletions
@@ -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 == ""
+94
View File
@@ -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')
})
})
+25 -2
View File
@@ -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 });
});
});