Skip to main content
Glama
test_response_shapes.py13.2 kB
""" Unit tests for response shape consistency. Validates that all response objects follow consistent structure and that error/success responses maintain same shape. """ import pytest from pydantic import ValidationError from mcp_debug_tool.schemas import ( BreakpointResponse, ContinueResponse, EndSessionResponse, ExecutionError, FrameInfo, SessionStateResponse, SessionStatus, StartSessionResponse, ) class TestResponseShapes: """Tests for response object shapes and consistency.""" def test_start_session_response_shape(self): """Test StartSessionResponse has required fields.""" response = StartSessionResponse(sessionId="test-session-123") # Verify required fields assert hasattr(response, "sessionId") assert response.sessionId == "test-session-123" assert isinstance(response.sessionId, str) def test_breakpoint_response_shape(self): """Test BreakpointResponse has all expected fields.""" response = BreakpointResponse( hit=True, frameInfo=FrameInfo(file="test.py", line=1), locals={"x": {"type": "int", "repr": "1", "isTruncated": False}}, completed=False, error=None, ) # Verify all fields exist assert hasattr(response, "hit") assert hasattr(response, "frameInfo") assert hasattr(response, "locals") assert hasattr(response, "completed") assert hasattr(response, "error") # Verify types assert isinstance(response.hit, bool) assert response.frameInfo is None or isinstance(response.frameInfo, FrameInfo) assert response.locals is None or isinstance(response.locals, dict) assert isinstance(response.completed, bool) assert response.error is None or isinstance(response.error, ExecutionError) def test_breakpoint_response_with_error(self): """Test BreakpointResponse with error.""" error = ExecutionError( type="ValueError", message="Invalid input", traceback=None, ) response = BreakpointResponse( hit=False, frameInfo=None, locals=None, completed=True, error=error, ) assert response.hit is False assert response.error is not None assert response.error.type == "ValueError" assert response.error.message == "Invalid input" def test_breakpoint_response_minimal(self): """Test BreakpointResponse with minimal fields.""" response = BreakpointResponse(hit=False, completed=False) # Required fields only assert response.hit is False assert response.completed is False # Optional fields default to None assert response.frameInfo is None assert response.locals is None assert response.error is None def test_continue_response_shape(self): """Test ContinueResponse has all expected fields.""" response = ContinueResponse( hit=True, completed=False, frameInfo=FrameInfo(file="test.py", line=5), locals={"y": {"type": "int", "repr": "2", "isTruncated": False}}, error=None, ) # Verify all fields assert hasattr(response, "hit") assert hasattr(response, "completed") assert hasattr(response, "frameInfo") assert hasattr(response, "locals") assert hasattr(response, "error") def test_continue_response_with_error(self): """Test ContinueResponse with error.""" error = ExecutionError( type="TimeoutError", message="Timeout", traceback=None, ) response = ContinueResponse( hit=False, completed=False, error=error, ) assert response.error is not None assert response.error.type == "TimeoutError" def test_execution_error_shape(self): """Test ExecutionError has required fields.""" error = ExecutionError( type="RuntimeError", message="Something went wrong", traceback="File ...", ) assert hasattr(error, "type") assert hasattr(error, "message") assert hasattr(error, "traceback") assert isinstance(error.type, str) assert isinstance(error.message, str) assert error.traceback is None or isinstance(error.traceback, str) def test_execution_error_minimal(self): """Test ExecutionError with minimal fields.""" error = ExecutionError( type="Error", message="Error message", ) assert error.type == "Error" assert error.message == "Error message" assert error.traceback is None def test_frame_info_shape(self): """Test FrameInfo has required fields.""" frame = FrameInfo(file="script.py", line=10) assert hasattr(frame, "file") assert hasattr(frame, "line") assert isinstance(frame.file, str) assert isinstance(frame.line, int) def test_session_state_response_shape(self): """Test SessionStateResponse has expected fields.""" response = SessionStateResponse( status=SessionStatus.PAUSED, lastBreakpoint=None, timings=None, ) assert hasattr(response, "status") assert hasattr(response, "lastBreakpoint") assert hasattr(response, "timings") assert isinstance(response.status, SessionStatus) def test_end_session_response_shape(self): """Test EndSessionResponse has required fields.""" response = EndSessionResponse(ended=True) assert hasattr(response, "ended") assert response.ended is True def test_responses_are_serializable(self): """Test that responses can be serialized to dict.""" response = BreakpointResponse( hit=True, frameInfo=FrameInfo(file="test.py", line=1), locals={"x": {"type": "int", "repr": "1", "isTruncated": False}}, completed=False, error=None, ) # Should be able to convert to dict (for JSON serialization) data = response.model_dump() assert isinstance(data, dict) assert "hit" in data assert "frameInfo" in data assert "locals" in data def test_error_response_is_serializable(self): """Test that error responses can be serialized.""" error = ExecutionError( type="ValueError", message="Bad value", traceback=None, ) data = error.model_dump() assert isinstance(data, dict) assert data["type"] == "ValueError" assert data["message"] == "Bad value" class TestResponseConsistency: """Tests for consistency across different response types.""" def test_success_and_error_responses_have_same_structure(self): """Test that success and error responses have compatible structure.""" # Success response success = BreakpointResponse( hit=True, frameInfo=FrameInfo(file="test.py", line=1), locals={}, completed=False, error=None, ) # Error response error = BreakpointResponse( hit=False, frameInfo=None, locals=None, completed=False, error=ExecutionError( type="Error", message="Error", ), ) # Both have same fields success_fields = set(success.model_fields.keys()) error_fields = set(error.model_fields.keys()) assert success_fields == error_fields def test_breakpoint_and_continue_responses_have_same_fields(self): """Test that breakpoint and continue responses have same structure.""" bp_response = BreakpointResponse(hit=True) cont_response = ContinueResponse(hit=True, completed=False) # Extract field names bp_fields = set(bp_response.model_fields.keys()) cont_fields = set(cont_response.model_fields.keys()) # Should have mostly same fields (continue has completed) common_fields = {"hit", "frameInfo", "locals", "error"} assert common_fields.issubset(bp_fields) assert common_fields.issubset(cont_fields) def test_error_field_is_optional_everywhere(self): """Test that error field is optional in all responses.""" # Breakpoint without error bp1 = BreakpointResponse(hit=True) assert bp1.error is None # Breakpoint with error bp2 = BreakpointResponse( hit=False, error=ExecutionError(type="Error", message="msg"), ) assert bp2.error is not None # Continue without error cont1 = ContinueResponse(hit=True, completed=False) assert cont1.error is None # Continue with error cont2 = ContinueResponse( hit=False, completed=False, error=ExecutionError(type="Error", message="msg"), ) assert cont2.error is not None def test_frame_info_field_is_optional(self): """Test that frameInfo is optional.""" # With frame info response1 = BreakpointResponse( hit=True, frameInfo=FrameInfo(file="test.py", line=1), ) assert response1.frameInfo is not None # Without frame info response2 = BreakpointResponse(hit=False) assert response2.frameInfo is None def test_locals_field_is_optional(self): """Test that locals field is optional.""" # With locals response1 = BreakpointResponse( hit=True, locals={"x": {"type": "int", "repr": "1", "isTruncated": False}}, ) assert response1.locals is not None # Without locals response2 = BreakpointResponse(hit=False) assert response2.locals is None class TestResponseFieldTypes: """Tests for response field types.""" def test_hit_field_is_boolean(self): """Test that hit field is boolean.""" response1 = BreakpointResponse(hit=True) assert isinstance(response1.hit, bool) response2 = BreakpointResponse(hit=False) assert isinstance(response2.hit, bool) def test_completed_field_is_boolean(self): """Test that completed field is boolean.""" response1 = BreakpointResponse(hit=True, completed=True) assert isinstance(response1.completed, bool) response2 = BreakpointResponse(hit=False, completed=False) assert isinstance(response2.completed, bool) def test_locals_field_is_dict(self): """Test that locals field is dict.""" response = BreakpointResponse( hit=True, locals={"x": {"type": "int", "repr": "1", "isTruncated": False}}, ) assert isinstance(response.locals, dict) def test_session_id_is_string(self): """Test that sessionId is string.""" response = StartSessionResponse(sessionId="abc123") assert isinstance(response.sessionId, str) def test_error_type_and_message_are_strings(self): """Test that error fields are strings.""" error = ExecutionError( type="ValueError", message="Invalid value", ) assert isinstance(error.type, str) assert isinstance(error.message, str) def test_frame_file_and_line(self): """Test that frame file is string and line is int.""" frame = FrameInfo(file="test.py", line=42) assert isinstance(frame.file, str) assert isinstance(frame.line, int) class TestResponseValidation: """Tests for response validation.""" def test_response_requires_hit_field(self): """Test that BreakpointResponse requires hit field.""" # Missing hit field should fail with ValidationError with pytest.raises(ValidationError): BreakpointResponse() def test_response_requires_completed_for_continue(self): """Test that ContinueResponse requires certain fields.""" # Continue requires at least hit and completed with pytest.raises(ValidationError): ContinueResponse() def test_error_requires_type_and_message(self): """Test that ExecutionError requires type and message.""" # Missing type should fail with pytest.raises(ValidationError): ExecutionError(message="msg") # Missing message should fail with pytest.raises(ValidationError): ExecutionError(type="Error") def test_frame_info_requires_file_and_line(self): """Test that FrameInfo requires file and line.""" # Missing file should fail with pytest.raises(ValidationError): FrameInfo(line=1) # Missing line should fail with pytest.raises(ValidationError): FrameInfo(file="test.py")

Latest Blog Posts

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/Kaina3/Debug-MCP'

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