Skip to main content
Glama

Jentic

Official
by jentic
test_tool_execution.py41.4 kB
"""Unit tests for the TaskExecutor class in tool_execution.py.""" """MOSTLY AI GENERATED""" from unittest.mock import AsyncMock, MagicMock, patch import pytest import pytest_asyncio from jentic.lib.agent_runtime.tool_execution import ( TaskExecutor, ) from jentic.lib.agent_runtime.api_hub import JenticAPIClient from jentic.lib.models import ( AssociatedFiles, FileId, GetFilesResponse, FileEntry, OperationEntry, WorkflowExecutionDetails, ) from arazzo_runner import ( WorkflowExecutionResult as OakWorkflowExecutionResult, WorkflowExecutionStatus, ) @pytest.fixture def minimal_config(): """Provide a minimal configuration for testing.""" return {"runtime": {"log_level": "DEBUG"}, "test_api": {"base_url": "https://api.example.com"}} @pytest.fixture def comprehensive_config(): """Provide a more comprehensive configuration for testing.""" return { "runtime": {"log_level": "INFO", "tool_format": "chatgpt"}, "test_api": { "base_url": "https://api.example.com", "auth": {"type": "basic", "username": "test_user", "password": "test_password"}, }, "weather_api": { "base_url": "https://weather.example.com", "auth": {"type": "bearer", "token": "test_token"}, }, "email_api": { "base_url": "https://email.example.com", "auth": { "type": "oauth2", "client_id": "client_123", "client_secret": "secret_456", "scopes": ["read", "write"], }, }, } @pytest_asyncio.fixture async def mock_api_hub_client(): """Create a mocked JenticAPIClient.""" with patch( "jentic.lib.agent_runtime.tool_execution.JenticAPIClient", autospec=True ) as mock_client_class: mock_client = AsyncMock(spec=JenticAPIClient) mock_client_class.return_value = mock_client yield mock_client class TestWorkflowExecution: """Test suite for workflow execution.""" @pytest.mark.asyncio async def test_execute_workflow_success(self, mock_api_hub_client): """Test successful workflow execution.""" # Prepare test data workflow_id = "test_workflow_uuid" # External UUID friendly_workflow_id = "internal_test_workflow" # Internal ID from API parameters = {"param1": "value1"} mock_arazzo_doc = {"info": "mock arazzo"} mock_source_descriptions = {"mock_source": {"openapi": "3.0"}} # Setup mock API Hub response for the renamed method mock_api_hub_client.get_execution_details_for_workflow.return_value = ( WorkflowExecutionDetails( arazzo_doc=mock_arazzo_doc, source_descriptions=mock_source_descriptions, friendly_workflow_id=friendly_workflow_id, ) ) # Create a mock runner with patch("jentic.lib.agent_runtime.tool_execution.ArazzoRunner") as mock_runner_class: mock_runner = MagicMock() # ArazzoRunner.execute_workflow is synchronous # Update return value structure for ArazzoRunner mock_runner.execute_workflow.return_value = OakWorkflowExecutionResult( status=WorkflowExecutionStatus.WORKFLOW_COMPLETE, workflow_id=friendly_workflow_id, outputs={"final_output": "success"}, ) mock_runner_class.return_value = mock_runner # Create the tool executor executor = TaskExecutor(mock_api_hub_client) # Pass required api_hub_client # Call the method result = await executor.execute_workflow(workflow_id, parameters) # Verify the calls mock_api_hub_client.get_execution_details_for_workflow.assert_called_once_with( workflow_id ) mock_runner_class.assert_called_once_with( arazzo_doc=mock_arazzo_doc, source_descriptions=mock_source_descriptions, ) # Verify execute_workflow is called with the INTERNAL ID mock_runner.execute_workflow.assert_called_once_with( workflow_id=friendly_workflow_id, inputs=parameters ) # Verify the result assert result.success is True assert result.output == {"final_output": "success"} assert result.error is None assert result.step_results is None # Not populated on success by TaskExecutor assert result.inputs is None # Not populated on success by TaskExecutor @pytest.mark.asyncio async def test_execute_workflow_runner_returns_error(self, mock_api_hub_client): """Test workflow execution when the runner returns an error status.""" workflow_id = "test_workflow_runner_error_uuid" friendly_workflow_id = "internal_test_workflow_runner_error" parameters = {"param1": "value1"} mock_arazzo_doc = {"info": "mock arazzo runner error"} mock_source_descriptions = {"mock_source_runner_error": {}} expected_step_outputs = {"step1": "failed due to X"} expected_error_message = "Runner processing failed" # Setup mock API Hub response mock_api_hub_client.get_execution_details_for_workflow.return_value = ( WorkflowExecutionDetails( arazzo_doc=mock_arazzo_doc, source_descriptions=mock_source_descriptions, friendly_workflow_id=friendly_workflow_id, ) ) # Create a mock runner that returns an error status with patch("jentic.lib.agent_runtime.tool_execution.ArazzoRunner") as mock_runner_class: mock_runner = MagicMock() mock_runner.execute_workflow.return_value = OakWorkflowExecutionResult( status=WorkflowExecutionStatus.ERROR, workflow_id=friendly_workflow_id, error=expected_error_message, outputs=None, step_outputs=expected_step_outputs, inputs=parameters, ) mock_runner_class.return_value = mock_runner # Create the tool executor executor = TaskExecutor(mock_api_hub_client) # Call the method result = await executor.execute_workflow(workflow_id, parameters) # Verify the calls mock_api_hub_client.get_execution_details_for_workflow.assert_called_once_with( workflow_id ) mock_runner_class.assert_called_once_with( arazzo_doc=mock_arazzo_doc, source_descriptions=mock_source_descriptions, ) # Verify execute_workflow is called with the INTERNAL ID mock_runner.execute_workflow.assert_called_once_with( workflow_id=friendly_workflow_id, inputs=parameters ) # Verify the result assert result.success is False assert result.error == expected_error_message assert result.output is None assert result.step_results == expected_step_outputs assert result.inputs == parameters @pytest.mark.asyncio async def test_execute_workflow_api_details_error(self, mock_api_hub_client): """Test workflow execution when fetching execution details fails.""" workflow_id = "test_workflow_api_fail" parameters = {"param1": "value1"} # Setup mock API Hub to return an empty dictionary (details not found) mock_api_hub_client.get_execution_details_for_workflow.return_value = None # Patch ArazzoRunner to ensure it's not called with patch("jentic.lib.agent_runtime.tool_execution.ArazzoRunner") as mock_runner_class: # Create the tool executor executor = TaskExecutor(mock_api_hub_client) # Call the method result = await executor.execute_workflow(workflow_id, parameters) # Assertions mock_api_hub_client.get_execution_details_for_workflow.assert_called_once_with( workflow_id ) mock_runner_class.assert_not_called() assert result.success is False assert f"Execution details not found for workflow {workflow_id}" == result.error @pytest.mark.asyncio async def test_execute_workflow_exception(self, mock_api_hub_client): """Test workflow execution when an unexpected exception occurs (e.g., runner init).""" workflow_id = "test_workflow_unexpected_fail" friendly_workflow_id = "internal_unexpected_fail" parameters = {"param1": "value1"} mock_arazzo_doc = {"info": "mock arazzo unexpected"} mock_source_descriptions = {} # Setup mock API Hub response for the renamed method mock_api_hub_client.get_execution_details_for_workflow.return_value = ( WorkflowExecutionDetails( arazzo_doc=mock_arazzo_doc, source_descriptions=mock_source_descriptions, friendly_workflow_id=friendly_workflow_id, ) ) # Mock runner class to raise an exception during instantiation with patch("jentic.lib.agent_runtime.tool_execution.ArazzoRunner") as mock_runner_class: mock_runner_class.side_effect = Exception("Runner Init Error") # Create the tool executor executor = TaskExecutor(mock_api_hub_client) # Call the method result = await executor.execute_workflow(workflow_id, parameters) # Verify the calls mock_api_hub_client.get_execution_details_for_workflow.assert_called_once_with( workflow_id ) # Runner execute is not called if init fails # Verify the result assert result.success is False assert "Runner Init Error" in result.error class TestCompleteWorkflowExecution: """Test suite for complete workflow execution scenarios.""" @pytest.mark.asyncio async def test_complete_execution_with_multiple_steps(self, mock_api_hub_client): """Test a complete workflow execution that conceptually involves multiple steps (mocked as one runner call).""" workflow_id = "multi_step_workflow_uuid" friendly_workflow_id = "internal_multi_step" parameters = {"initial_input": "start"} arazzo_doc = {"info": "multi-step arazzo"} source_descriptions = {"multi_step_source": {}} # Setup mock API Hub response mock_api_hub_client.get_execution_details_for_workflow.return_value = ( WorkflowExecutionDetails( arazzo_doc=arazzo_doc, source_descriptions=source_descriptions, friendly_workflow_id=friendly_workflow_id, ) ) # Mock the runner with patch("jentic.lib.agent_runtime.tool_execution.ArazzoRunner") as mock_runner_class: mock_runner = MagicMock() # Update return value structure mock_runner.execute_workflow.return_value = OakWorkflowExecutionResult( status=WorkflowExecutionStatus.WORKFLOW_COMPLETE, workflow_id=friendly_workflow_id, outputs={"final_output": "success"}, ) mock_runner_class.return_value = mock_runner # Create the tool executor executor = TaskExecutor(mock_api_hub_client) # Execute the workflow result = await executor.execute_workflow(workflow_id, parameters) # Assertions mock_api_hub_client.get_execution_details_for_workflow.assert_called_once_with( workflow_id ) mock_runner_class.assert_called_once_with( arazzo_doc=arazzo_doc, source_descriptions=source_descriptions, ) mock_runner.execute_workflow.assert_called_once_with( workflow_id=friendly_workflow_id, inputs=parameters ) assert result.success is True assert result.output == {"final_output": "success"} @pytest.mark.asyncio async def test_workflow_with_empty_parameters(self, mock_api_hub_client): """Test executing a workflow with an empty parameters dictionary.""" workflow_id = "empty_params_workflow_uuid" friendly_workflow_id = "internal_empty_params" mock_arazzo_doc = {"some_key": "some_value"} mock_source_descriptions = {} # Setup mock API Hub response mock_api_hub_client.get_execution_details_for_workflow.return_value = ( WorkflowExecutionDetails( arazzo_doc=mock_arazzo_doc, source_descriptions=mock_source_descriptions, friendly_workflow_id=friendly_workflow_id, ) ) # Mock the runner with patch("jentic.lib.agent_runtime.tool_execution.ArazzoRunner") as mock_runner_class: mock_runner = MagicMock() # Update return value structure mock_runner.execute_workflow.return_value = OakWorkflowExecutionResult( status=WorkflowExecutionStatus.WORKFLOW_COMPLETE, workflow_id=friendly_workflow_id, outputs={"output": "empty_params_success"}, ) mock_runner_class.return_value = mock_runner # Create the tool executor executor = TaskExecutor(mock_api_hub_client) # Call the method with empty parameters result = await executor.execute_workflow(workflow_id, {}) # Assertions mock_api_hub_client.get_execution_details_for_workflow.assert_called_once_with( workflow_id ) mock_runner.execute_workflow.assert_called_once_with( workflow_id=friendly_workflow_id, inputs={} ) assert result.success is True assert result.output == {"output": "empty_params_success"} @pytest.mark.asyncio async def test_workflow_execution_with_no_outputs(self, mock_api_hub_client): """Test workflow execution where the runner returns no specific outputs.""" workflow_id = "no_output_workflow_uuid" friendly_workflow_id = "internal_no_output" parameters = {"input": "data"} mock_arazzo_doc = {"some_key": "some_value"} mock_source_descriptions = {} # Setup mock API Hub response mock_api_hub_client.get_execution_details_for_workflow.return_value = ( WorkflowExecutionDetails( arazzo_doc=mock_arazzo_doc, source_descriptions=mock_source_descriptions, friendly_workflow_id=friendly_workflow_id, ) ) # Mock the runner to return an empty dictionary for outputs with patch("jentic.lib.agent_runtime.tool_execution.ArazzoRunner") as mock_runner_class: mock_runner = MagicMock() # Update return value structure mock_runner.execute_workflow.return_value = OakWorkflowExecutionResult( status=WorkflowExecutionStatus.WORKFLOW_COMPLETE, workflow_id=friendly_workflow_id, outputs={}, ) mock_runner_class.return_value = mock_runner # Create the tool executor executor = TaskExecutor(mock_api_hub_client) # Call the method result = await executor.execute_workflow(workflow_id, parameters) # Assertions mock_api_hub_client.get_execution_details_for_workflow.assert_called_once_with( workflow_id ) mock_runner.execute_workflow.assert_called_once_with( workflow_id=friendly_workflow_id, inputs=parameters ) assert result.success is True assert result.output == {} assert result.error is None class TestEdgeCases: """Tests covering edge cases and specific scenarios for workflow execution.""" @pytest.mark.asyncio async def test_workflow_with_empty_parameters(self, mock_api_hub_client): """Test executing a workflow with an empty parameters dictionary.""" workflow_id = "edge_empty_params_workflow_uuid" friendly_workflow_id = "internal_edge_empty_params" mock_arazzo_doc = {"some_key": "some_value"} mock_source_descriptions = {} # Setup mock API Hub response mock_api_hub_client.get_execution_details_for_workflow.return_value = ( WorkflowExecutionDetails( arazzo_doc=mock_arazzo_doc, source_descriptions=mock_source_descriptions, friendly_workflow_id=friendly_workflow_id, ) ) # Mock the runner with patch("jentic.lib.agent_runtime.tool_execution.ArazzoRunner") as mock_runner_class: mock_runner = MagicMock() # Update return value structure mock_runner.execute_workflow.return_value = OakWorkflowExecutionResult( status=WorkflowExecutionStatus.WORKFLOW_COMPLETE, workflow_id=friendly_workflow_id, outputs={"output": "empty_params_success"}, ) mock_runner_class.return_value = mock_runner # Create the tool executor executor = TaskExecutor(mock_api_hub_client) # Call the method with empty parameters result = await executor.execute_workflow(workflow_id, {}) # Assertions mock_api_hub_client.get_execution_details_for_workflow.assert_called_once_with( workflow_id ) mock_runner.execute_workflow.assert_called_once_with( workflow_id=friendly_workflow_id, inputs={} ) assert result.success is True assert result.output == {"output": "empty_params_success"} @pytest.mark.asyncio async def test_workflow_with_alternate_complete_status(self, mock_api_hub_client): """Test workflow execution with an alternate (but valid) completion status.""" workflow_id = "alt_complete_workflow_uuid" friendly_workflow_id = "internal_alt_complete" mock_arazzo_doc = {"some_key": "some_value"} mock_source_descriptions = {} # Setup mock API Hub response mock_api_hub_client.get_execution_details_for_workflow.return_value = ( WorkflowExecutionDetails( arazzo_doc=mock_arazzo_doc, source_descriptions=mock_source_descriptions, friendly_workflow_id=friendly_workflow_id, ) ) # Create mock runner with patch("jentic.lib.agent_runtime.tool_execution.ArazzoRunner") as mock_runner_class: mock_runner = MagicMock() # Runner returns final result directly # Update return value structure mock_runner.execute_workflow.return_value = OakWorkflowExecutionResult( status=WorkflowExecutionStatus.WORKFLOW_COMPLETE, workflow_id=friendly_workflow_id, outputs={"result": "success"}, ) mock_runner_class.return_value = mock_runner # Create the tool executor executor = TaskExecutor(mock_api_hub_client) # Call the method result = await executor.execute_workflow(workflow_id, {}) # Assertions mock_api_hub_client.get_execution_details_for_workflow.assert_called_once_with( workflow_id ) mock_runner.execute_workflow.assert_called_once_with( workflow_id=friendly_workflow_id, inputs={} ) assert result.success is True assert result.output == {"result": "success"} class TestOperationExecution: """Test suite for operation execution.""" @pytest.mark.asyncio async def test_execute_operation_success(self, mock_api_hub_client): """Test successful operation execution with a 200 status and body.""" operation_uuid = "op_success_uuid" inputs = {"param": "value"} mock_openapi_content = {"openapi": "3.0.0", "info": {"title": "Test API"}} mock_operation_entry = OperationEntry( api_version_id="v1", id=operation_uuid, method="GET", path="/test", files=AssociatedFiles(open_api=[FileId(id="openapi_file_id")]), ) mock_exec_files_response = GetFilesResponse( workflows={}, operations={operation_uuid: mock_operation_entry}, files={ "open_api": { "openapi_file_id": FileEntry( id="openapi_file_id", filename="spec.json", type="open_api", content=mock_openapi_content, ) } }, ) mock_api_hub_client.get_execution_files.return_value = mock_exec_files_response oak_response_body = {"data": "success_data"} oak_full_response = {"status_code": 200, "body": oak_response_body, "headers": {}} with patch("jentic.lib.agent_runtime.tool_execution.ArazzoRunner") as mock_runner_class: mock_runner = MagicMock() mock_runner.execute_operation.return_value = oak_full_response mock_runner_class.return_value = mock_runner executor = TaskExecutor(api_hub_client=mock_api_hub_client) result = await executor.execute_operation(operation_uuid, inputs) mock_api_hub_client.get_execution_files.assert_called_once_with( operation_uuids=[operation_uuid] ) mock_runner_class.assert_called_once_with( source_descriptions={"default": mock_openapi_content} ) mock_runner.execute_operation.assert_called_once_with( inputs=inputs, operation_path=f"{mock_operation_entry.method} {mock_operation_entry.path}", ) assert result.success is True assert result.status_code == 200 assert result.output == oak_response_body assert result.error is None assert result.inputs == inputs @pytest.mark.asyncio async def test_execute_operation_success_no_body(self, mock_api_hub_client): """Test successful operation execution with 200 status but no 'body' key in OAK result.""" operation_uuid = "op_success_no_body_uuid" inputs = {} mock_openapi_content = {"openapi": "3.0.0"} mock_operation_entry = OperationEntry( api_version_id="v1", id=operation_uuid, method="POST", path="/submit", files=AssociatedFiles(open_api=[FileId(id="f1")]), ) mock_exec_files_response = GetFilesResponse( workflows={}, operations={operation_uuid: mock_operation_entry}, files={ "open_api": { "f1": FileEntry( id="f1", filename="spec.json", type="open_api", content=mock_openapi_content ) } }, ) mock_api_hub_client.get_execution_files.return_value = mock_exec_files_response oak_full_response = { "status_code": 204, "headers": {"X-Custom": "value"}, } # No body for 204 with patch("jentic.lib.agent_runtime.tool_execution.ArazzoRunner") as mock_runner_class: mock_runner = MagicMock() mock_runner.execute_operation.return_value = oak_full_response mock_runner_class.return_value = mock_runner executor = TaskExecutor(api_hub_client=mock_api_hub_client) result = await executor.execute_operation(operation_uuid, inputs) assert result.success is True assert result.status_code == 204 assert result.output == oak_full_response # Should return the full OAK result assert result.error is None assert result.inputs == inputs @pytest.mark.asyncio async def test_execute_operation_api_error_4xx(self, mock_api_hub_client): """Test operation execution with a 400 client error.""" operation_uuid = "op_client_error_uuid" inputs = {"bad_param": "invalid"} mock_openapi_content = {"openapi": "3.0.0"} mock_operation_entry = OperationEntry( api_version_id="v1", id=operation_uuid, method="GET", path="/test", files=AssociatedFiles(open_api=[FileId(id="f1")]), ) mock_exec_files_response = GetFilesResponse( workflows={}, operations={operation_uuid: mock_operation_entry}, files={ "open_api": { "f1": FileEntry( id="f1", filename="spec.json", type="open_api", content=mock_openapi_content ) } }, ) mock_api_hub_client.get_execution_files.return_value = mock_exec_files_response error_detail = {"error": "Bad Request", "message": "Invalid parameter provided"} oak_full_response = {"status_code": 400, "body": error_detail, "headers": {}} with patch("jentic.lib.agent_runtime.tool_execution.ArazzoRunner") as mock_runner_class: mock_runner = MagicMock() mock_runner.execute_operation.return_value = oak_full_response mock_runner_class.return_value = mock_runner executor = TaskExecutor(api_hub_client=mock_api_hub_client) result = await executor.execute_operation(operation_uuid, inputs) assert result.success is False assert result.status_code == 400 # The error message is now just the detail from the body assert result.error == error_detail["error"] assert result.output == oak_full_response # Full OAK response in output for context assert result.inputs == inputs @pytest.mark.asyncio async def test_execute_operation_api_error_5xx(self, mock_api_hub_client): """Test operation execution with a 500 server error.""" operation_uuid = "op_server_error_uuid" inputs = {} mock_openapi_content = {"openapi": "3.0.0"} mock_operation_entry = OperationEntry( api_version_id="v1", id=operation_uuid, method="GET", path="/status", files=AssociatedFiles(open_api=[FileId(id="f1")]), ) mock_exec_files_response = GetFilesResponse( workflows={}, operations={operation_uuid: mock_operation_entry}, files={ "open_api": { "f1": FileEntry( id="f1", filename="spec.json", type="open_api", content=mock_openapi_content ) } }, ) mock_api_hub_client.get_execution_files.return_value = mock_exec_files_response oak_full_response = {"status_code": 503, "body": "Service Unavailable", "headers": {}} with patch("jentic.lib.agent_runtime.tool_execution.ArazzoRunner") as mock_runner_class: mock_runner = MagicMock() mock_runner.execute_operation.return_value = oak_full_response mock_runner_class.return_value = mock_runner executor = TaskExecutor(api_hub_client=mock_api_hub_client) result = await executor.execute_operation(operation_uuid, inputs) assert result.success is False assert result.status_code == 503 assert result.error == "Service Unavailable" assert result.output == oak_full_response assert result.inputs == inputs @pytest.mark.asyncio async def test_execute_operation_status_code_as_string(self, mock_api_hub_client): """Test successful operation when status_code is a convertible string.""" operation_uuid = "op_status_string_uuid" inputs = {} mock_openapi_content = {"openapi": "3.0.0"} mock_operation_entry = OperationEntry( api_version_id="v1", id=operation_uuid, method="GET", path="/test", files=AssociatedFiles(open_api=[FileId(id="f1")]), ) mock_exec_files_response = GetFilesResponse( workflows={}, operations={operation_uuid: mock_operation_entry}, files={ "open_api": { "f1": FileEntry( id="f1", filename="spec.json", type="open_api", content=mock_openapi_content ) } }, ) mock_api_hub_client.get_execution_files.return_value = mock_exec_files_response oak_response_body = {"message": "OK"} oak_full_response = {"status_code": "201", "body": oak_response_body, "headers": {}} with patch("jentic.lib.agent_runtime.tool_execution.ArazzoRunner") as mock_runner_class: mock_runner = MagicMock() mock_runner.execute_operation.return_value = oak_full_response mock_runner_class.return_value = mock_runner executor = TaskExecutor(api_hub_client=mock_api_hub_client) result = await executor.execute_operation(operation_uuid, inputs) assert result.success is True assert result.status_code == 201 # Should be cast to int assert result.output == oak_response_body assert result.error is None assert result.inputs == inputs @pytest.mark.asyncio async def test_execute_operation_status_code_invalid_string(self, mock_api_hub_client): """Test failure when status_code is a non-convertible string.""" operation_uuid = "op_status_invalid_string_uuid" inputs = {} mock_openapi_content = {"openapi": "3.0.0"} mock_operation_entry = OperationEntry( api_version_id="v1", id=operation_uuid, method="GET", path="/test", files=AssociatedFiles(open_api=[FileId(id="f1")]), ) mock_exec_files_response = GetFilesResponse( workflows={}, operations={operation_uuid: mock_operation_entry}, files={ "open_api": { "f1": FileEntry( id="f1", filename="spec.json", type="open_api", content=mock_openapi_content ) } }, ) mock_api_hub_client.get_execution_files.return_value = mock_exec_files_response oak_full_response = {"status_code": "OK_NOT_A_NUMBER", "body": "Error", "headers": {}} with patch("jentic.lib.agent_runtime.tool_execution.ArazzoRunner") as mock_runner_class: mock_runner = MagicMock() mock_runner.execute_operation.return_value = oak_full_response mock_runner_class.return_value = mock_runner executor = TaskExecutor(api_hub_client=mock_api_hub_client) result = await executor.execute_operation(operation_uuid, inputs) assert result.success is False assert result.status_code is None # Status code is not set on casting failure assert "Invalid status_code format: 'OK_NOT_A_NUMBER'" in result.error assert result.output == oak_full_response # Full OAK response in output assert result.inputs == inputs @pytest.mark.asyncio async def test_execute_operation_missing_status_code(self, mock_api_hub_client): """Test behavior when ArazzoRunner result is missing 'status_code'.""" operation_uuid = "op_missing_status_uuid" inputs = {} mock_openapi_content = {"openapi": "3.0.0"} mock_operation_entry = OperationEntry( api_version_id="v1", id=operation_uuid, method="GET", path="/test", files=AssociatedFiles(open_api=[FileId(id="f1")]), ) mock_exec_files_response = GetFilesResponse( workflows={}, operations={operation_uuid: mock_operation_entry}, files={ "open_api": { "f1": FileEntry( id="f1", filename="spec.json", type="open_api", content=mock_openapi_content ) } }, ) mock_api_hub_client.get_execution_files.return_value = mock_exec_files_response oak_response_body = {"data": "some_data"} # OAK result missing status_code oak_full_response = {"body": oak_response_body, "headers": {}} with patch("jentic.lib.agent_runtime.tool_execution.ArazzoRunner") as mock_runner_class: mock_runner = MagicMock() mock_runner.execute_operation.return_value = oak_full_response mock_runner_class.return_value = mock_runner executor = TaskExecutor(api_hub_client=mock_api_hub_client) result = await executor.execute_operation(operation_uuid, inputs) # Current logic defaults to success if status_code is missing assert result.success is True assert result.status_code is None assert result.output == oak_response_body assert result.error is None assert result.inputs == inputs @pytest.mark.asyncio async def test_execute_operation_runner_returns_not_dict(self, mock_api_hub_client): """Test behavior when ArazzoRunner returns a non-dictionary result.""" operation_uuid = "op_runner_not_dict_uuid" inputs = {} mock_openapi_content = {"openapi": "3.0.0"} mock_operation_entry = OperationEntry( api_version_id="v1", id=operation_uuid, method="GET", path="/test", files=AssociatedFiles(open_api=[FileId(id="f1")]), ) mock_exec_files_response = GetFilesResponse( workflows={}, operations={operation_uuid: mock_operation_entry}, files={ "open_api": { "f1": FileEntry( id="f1", filename="spec.json", type="open_api", content=mock_openapi_content ) } }, ) mock_api_hub_client.get_execution_files.return_value = mock_exec_files_response # ArazzoRunner returns a string instead of a dict oak_non_dict_response = "This is not a dictionary" with patch("jentic.lib.agent_runtime.tool_execution.ArazzoRunner") as mock_runner_class: mock_runner = MagicMock() mock_runner.execute_operation.return_value = oak_non_dict_response mock_runner_class.return_value = mock_runner executor = TaskExecutor(api_hub_client=mock_api_hub_client) result = await executor.execute_operation(operation_uuid, inputs) assert result.success is False assert "object has no attribute 'get'" in result.error assert result.status_code is None assert result.output is None assert result.inputs == inputs @pytest.mark.asyncio async def test_execute_operation_no_operation_entry(self, mock_api_hub_client): """Test failure when operation_uuid is not found in API Hub response.""" operation_uuid = "op_not_found_uuid" inputs = {} # API Hub returns a response where the operation_uuid is not a key mock_exec_files_response = GetFilesResponse(workflows={}, operations={}, files={}) mock_api_hub_client.get_execution_files.return_value = mock_exec_files_response with patch("jentic.lib.agent_runtime.tool_execution.ArazzoRunner") as mock_runner_class: executor = TaskExecutor(api_hub_client=mock_api_hub_client) result = await executor.execute_operation(operation_uuid, inputs) mock_runner_class.assert_not_called() # ArazzoRunner should not be initialized or called assert result.success is False assert ( result.error == f"Operation ID {operation_uuid} not found in execution files response." ) assert result.status_code is None assert result.output is None assert result.inputs == inputs @pytest.mark.asyncio async def test_execute_operation_no_openapi_spec(self, mock_api_hub_client): """Test failure when OpenAPI spec content is missing.""" operation_uuid = "op_no_openapi_uuid" inputs = {} # OperationEntry exists, but its files list is empty or points to a non-existent/empty file mock_operation_entry = OperationEntry( api_version_id="v1", id=operation_uuid, method="GET", path="/test", files=AssociatedFiles(open_api=[FileId(id="openapi_file_id")]), # Points to a file ) mock_exec_files_response = GetFilesResponse( workflows={}, operations={operation_uuid: mock_operation_entry}, files={ "open_api": { "openapi_file_id": FileEntry( id="openapi_file_id", filename="spec.json", type="open_api", content={} ) } }, # File content is empty ) mock_api_hub_client.get_execution_files.return_value = mock_exec_files_response with patch("jentic.lib.agent_runtime.tool_execution.ArazzoRunner") as mock_runner_class: executor = TaskExecutor(api_hub_client=mock_api_hub_client) result = await executor.execute_operation(operation_uuid, inputs) mock_runner_class.assert_not_called() assert result.success is False assert result.error == f"OpenAPI spec not found for operation {operation_uuid}" assert result.status_code is None assert result.output is None assert result.inputs == inputs @pytest.mark.asyncio async def test_execute_operation_no_openapi_file_entry_in_files(self, mock_api_hub_client): """Test failure when OpenAPI spec file ID from operation_entry.files.open_api is not in exec_files_response.files.""" operation_uuid = "op_no_openapi_file_entry_uuid" inputs = {} mock_operation_entry = OperationEntry( api_version_id="v1", id=operation_uuid, method="GET", path="/test", files=AssociatedFiles(open_api=[FileId(id="actual_openapi_file_id")]), ) # The 'files' dict does not contain 'actual_openapi_file_id' under 'open_api' mock_exec_files_response = GetFilesResponse( workflows={}, operations={operation_uuid: mock_operation_entry}, files={ "open_api": { "some_other_file_id": FileEntry( id="some_other_file_id", filename="spec.json", type="open_api", content={"openapi": "3.0"}, ) } }, ) mock_api_hub_client.get_execution_files.return_value = mock_exec_files_response with patch("jentic.lib.agent_runtime.tool_execution.ArazzoRunner") as mock_runner_class: executor = TaskExecutor(api_hub_client=mock_api_hub_client) result = await executor.execute_operation(operation_uuid, inputs) mock_runner_class.assert_not_called() assert result.success is False assert result.error == f"OpenAPI spec not found for operation {operation_uuid}" assert result.status_code is None assert result.output is None assert result.inputs == inputs @pytest.mark.asyncio async def test_execute_operation_general_exception_api_call(self, mock_api_hub_client): """Test failure when get_execution_files raises an unexpected exception.""" operation_uuid = "op_general_exception_uuid" inputs = {} expected_exception = RuntimeError("Network Error") mock_api_hub_client.get_execution_files.side_effect = expected_exception with patch("jentic.lib.agent_runtime.tool_execution.ArazzoRunner") as mock_runner_class: executor = TaskExecutor(api_hub_client=mock_api_hub_client) result = await executor.execute_operation(operation_uuid, inputs) mock_runner_class.assert_not_called() assert result.success is False assert str(expected_exception) in result.error assert result.status_code is None assert result.output is None assert result.inputs == inputs if __name__ == "__main__": pytest.main(["-xvs", __file__])

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jentic/jentic-tools'

If you have feedback or need assistance with the MCP directory API, please join our Discord server