diff --git a/pyrit/prompt_target/http_target/httpx_api_target.py b/pyrit/prompt_target/http_target/httpx_api_target.py index bbf993928..393f947ba 100644 --- a/pyrit/prompt_target/http_target/httpx_api_target.py +++ b/pyrit/prompt_target/http_target/httpx_api_target.py @@ -105,6 +105,8 @@ async def send_prompt_async(self, *, message: Message) -> list[Message]: if isinstance(possible_path, str) and os.path.exists(possible_path): logger.info(f"HTTPXApiTarget: auto-using file_path from {possible_path}") self.file_path = possible_path + elif not os.path.exists(self.file_path): + raise FileNotFoundError(f"File not found: {self.file_path}") if not self.http_url: raise ValueError("No `http_url` provided for HTTPXApiTarget.") @@ -129,6 +131,7 @@ async def send_prompt_async(self, *, message: Message) -> list[Message]: method=self.method, url=self.http_url, headers=self.headers, + params=self.params, files=files, follow_redirects=True, ) @@ -139,7 +142,7 @@ async def send_prompt_async(self, *, message: Message) -> list[Message]: method=self.method, url=self.http_url, headers=self.headers, - params=self.params if self.method in {"GET", "HEAD"} else None, + params=self.params, json=self.json_data if self.method in {"POST", "PUT", "PATCH"} else None, data=self.form_data if self.method in {"POST", "PUT", "PATCH"} else None, follow_redirects=True, diff --git a/tests/unit/target/test_http_api_target.py b/tests/unit/target/test_http_api_target.py index c67919fe1..ad98884b3 100644 --- a/tests/unit/target/test_http_api_target.py +++ b/tests/unit/target/test_http_api_target.py @@ -45,6 +45,34 @@ async def test_send_prompt_async_file_upload(mock_request, patch_central_databas os.unlink(file_path) +@pytest.mark.asyncio +@patch("httpx.AsyncClient.request") +async def test_send_prompt_async_file_upload_preserves_query_params(mock_request, patch_central_database): + with tempfile.NamedTemporaryFile(delete=False) as tmp: + tmp.write(b"This is a mock PDF content") + tmp.flush() + file_path = tmp.name + + message_piece = MessagePiece(role="user", original_value="mock", converted_value=file_path) + message = Message(message_pieces=[message_piece]) + + mock_response = MagicMock() + mock_response.content = b'{"message": "File uploaded successfully"}' + mock_request.return_value = mock_response + + target = HTTPXAPITarget( + http_url="http://example.com/upload/", + method="POST", + params={"alpha": "1"}, + timeout=180, + ) + await target.send_prompt_async(message=message) + + assert mock_request.call_args.kwargs["params"] == {"alpha": "1"} + + os.unlink(file_path) + + @pytest.mark.asyncio @patch("httpx.AsyncClient.request") async def test_send_prompt_async_no_file(mock_request, patch_central_database): @@ -70,6 +98,55 @@ async def test_send_prompt_async_no_file(mock_request, patch_central_database): assert "Sample JSON response" in response_text +@pytest.mark.asyncio +@patch("httpx.AsyncClient.request") +async def test_send_prompt_async_preserves_query_params_for_post(mock_request, patch_central_database): + message_piece = MessagePiece(role="user", original_value="mock", converted_value="non_existent_file.pdf") + message = Message(message_pieces=[message_piece]) + + mock_response = MagicMock() + mock_response.content = b'{"status": "ok"}' + mock_request.return_value = mock_response + + target = HTTPXAPITarget( + http_url="http://example.com/data/", + method="POST", + params={"alpha": "1"}, + json_data={"payload": "value"}, + timeout=180, + ) + await target.send_prompt_async(message=message) + + mock_request.assert_called_once_with( + method="POST", + url="http://example.com/data/", + headers={}, + params={"alpha": "1"}, + json={"payload": "value"}, + data=None, + follow_redirects=True, + ) + + +@pytest.mark.asyncio +@patch("httpx.AsyncClient.request") +async def test_send_prompt_async_missing_explicit_file_path_raises(mock_request, patch_central_database): + message_piece = MessagePiece(role="user", original_value="mock", converted_value="trigger") + message = Message(message_pieces=[message_piece]) + + target = HTTPXAPITarget( + http_url="http://example.com/upload/", + method="POST", + file_path="/definitely/missing/file.pdf", + timeout=180, + ) + + with pytest.raises(FileNotFoundError, match="File not found"): + await target.send_prompt_async(message=message) + + mock_request.assert_not_called() + + @pytest.mark.asyncio @patch("httpx.AsyncClient.request") async def test_send_prompt_async_validation(mock_request, patch_central_database):