From c879b46b1346d312cf4a22053cc480dd71e9ffa7 Mon Sep 17 00:00:00 2001 From: jtstothard Date: Tue, 28 Apr 2026 12:17:54 +0100 Subject: [PATCH] fix: Black formatting + add coverage for common_utils and responses transformer - Fix Black formatting in responses/transformation.py (vision log line) - Add tests for get_copilot_default_headers (with/without conversation key) - Add tests for get_complete_url, supports_native_websocket, validate_environment error paths, map_openai_params --- .../responses/transformation.py | 4 +- ...github_copilot_responses_transformation.py | 58 +++++++++++++++++++ .../github_copilot/test_conversation_id.py | 39 +++++++++++++ 3 files changed, 98 insertions(+), 3 deletions(-) diff --git a/litellm/llms/github_copilot/responses/transformation.py b/litellm/llms/github_copilot/responses/transformation.py index c67747ff85c..f5e75eb2dc5 100644 --- a/litellm/llms/github_copilot/responses/transformation.py +++ b/litellm/llms/github_copilot/responses/transformation.py @@ -115,9 +115,7 @@ class GithubCopilotResponsesAPIConfig(OpenAIResponsesAPIConfig): # Add vision header if input contains images if self._has_vision_input(input): headers["copilot-vision-request"] = "true" - verbose_logger.debug( - "GitHub Copilot Responses API: Enabled vision request" - ) + verbose_logger.debug("GitHub Copilot Responses API: Enabled vision request") # Call parent to get request body (validates input, handles reasoning items) return super().transform_responses_api_request( diff --git a/tests/test_litellm/llms/github_copilot/responses/test_github_copilot_responses_transformation.py b/tests/test_litellm/llms/github_copilot/responses/test_github_copilot_responses_transformation.py index d6dd9effd61..b287cf33b84 100644 --- a/tests/test_litellm/llms/github_copilot/responses/test_github_copilot_responses_transformation.py +++ b/tests/test_litellm/llms/github_copilot/responses/test_github_copilot_responses_transformation.py @@ -578,3 +578,61 @@ class TestEncryptedContentEdgeCases: assert ( result1["encrypted_content"] != result2["encrypted_content"] ), "Each reasoning item must preserve its own encrypted_content independently" + + +class TestResponsesAPIMiscMethods: + """Cover misc methods with low coverage in responses/transformation.py.""" + + def setup_method(self): + self.config = GithubCopilotResponsesAPIConfig() + + def test_supports_native_websocket_is_false(self): + assert self.config.supports_native_websocket() is False + + def test_get_complete_url_default_base(self, mock_authenticator_class): + url = self.config.get_complete_url(api_base=None, litellm_params={}) + assert url == "https://api.githubcopilot.com/responses" + + def test_get_complete_url_custom_base(self, mock_authenticator_class): + url = self.config.get_complete_url( + api_base="https://custom.example.com/v1/", litellm_params={} + ) + assert url == "https://custom.example.com/v1/responses" + + def test_validate_environment_no_api_key_raises(self, mock_authenticator_class): + mock_authenticator_class.return_value.get_api_key.return_value = None + with pytest.raises(AuthenticationError): + self.config.validate_environment( + headers={}, model="gpt-4", litellm_params=None + ) + + def test_validate_environment_get_api_key_error_raises( + self, mock_authenticator_class + ): + mock_authenticator_class.return_value.get_api_key.side_effect = GetAPIKeyError( + status_code=401, message="key expired" + ) + with pytest.raises(AuthenticationError): + self.config.validate_environment( + headers={}, model="gpt-4", litellm_params=None + ) + + def test_validate_environment_with_conversation_key(self, mock_authenticator_class): + mock_authenticator_class.return_value.get_api_key.return_value = "test-key" + lp = GenericLiteLLMParams(metadata={"copilot_conversation_id": "test-conv"}) + headers = self.config.validate_environment( + headers={}, model="gpt-4", litellm_params=lp + ) + assert "x-conversation-id" in headers + + def test_map_openai_params_returns_dict(self): + result = self.config.map_openai_params( + response_api_optional_params={"temperature": 0.5}, + model="gpt-4", + drop_params=False, + ) + assert result == {"temperature": 0.5} + + def test_get_supported_openai_params_returns_list(self): + result = self.config.get_supported_openai_params("gpt-4") + assert isinstance(result, list) diff --git a/tests/test_litellm/llms/github_copilot/test_conversation_id.py b/tests/test_litellm/llms/github_copilot/test_conversation_id.py index 7f7f90ed016..186acffcbd0 100644 --- a/tests/test_litellm/llms/github_copilot/test_conversation_id.py +++ b/tests/test_litellm/llms/github_copilot/test_conversation_id.py @@ -139,3 +139,42 @@ class TestDetermineXInitiator: def test_empty_list_returns_user(self): assert determine_x_initiator([]) == "user" + + def test_non_dict_items_ignored(self): + """Non-dict items in the list should be skipped.""" + assert determine_x_initiator(["hello", 42, None]) == "user" + + def test_system_plus_user_returns_user(self): + """System + user is still user-initiated.""" + messages = [ + {"role": "system", "content": "You are helpful"}, + {"role": "user", "content": "Hi"}, + ] + assert determine_x_initiator(messages) == "user" + + +class TestGetCopilotDefaultHeaders: + """Tests for get_copilot_default_headers in common_utils.py.""" + + def test_basic_headers_without_conversation_key(self): + headers = get_copilot_default_headers(api_key="test-key-123") + + assert headers["Authorization"] == "Bearer test-key-123" + assert headers["content-type"] == "application/json" + assert headers["copilot-integration-id"] == "vscode-chat" + assert "x-request-id" in headers + assert COPILOT_CONVERSATION_ID_HEADER not in headers + + def test_headers_with_conversation_key(self): + headers = get_copilot_default_headers( + api_key="test-key", conversation_key="my-conv" + ) + + assert COPILOT_CONVERSATION_ID_HEADER in headers + conv_id = headers[COPILOT_CONVERSATION_ID_HEADER] + assert len(conv_id) == 36 # UUID format + + def test_same_conversation_key_returns_same_id(self): + h1 = get_copilot_default_headers(api_key="k", conversation_key="stable-key") + h2 = get_copilot_default_headers(api_key="k", conversation_key="stable-key") + assert h1[COPILOT_CONVERSATION_ID_HEADER] == h2[COPILOT_CONVERSATION_ID_HEADER]