Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion pyrit/prompt_target/http_target/httpx_api_target.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.")
Expand All @@ -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,
)
Expand All @@ -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,
Expand Down
77 changes: 77 additions & 0 deletions tests/unit/target/test_http_api_target.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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):
Expand Down
Loading