Skip to content

Commit baa3c68

Browse files
test(http_request): add integration test for http_request tool (#113)
* test(http_request): add integration test for http_request tool * test(http_request): use self host server to handle the tests, fix mem0 unit test fail when user has faiss installed --------- Co-authored-by: Jack Yuan <[email protected]>
1 parent 22c63e8 commit baa3c68

File tree

2 files changed

+156
-6
lines changed

2 files changed

+156
-6
lines changed

tests-integ/test_http_request.py

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
"""Integration tests for HTTP request functionality using Strands Agent.
2+
3+
This module contains integration tests that verify the HTTP request tool's ability
4+
to handle various HTTP methods (GET, POST, PUT) and operations like downloading
5+
files and interacting with AWS S3 services.
6+
"""
7+
8+
import json
9+
import os
10+
import socket
11+
import threading
12+
import time
13+
from http.server import BaseHTTPRequestHandler, HTTPServer
14+
15+
import pytest
16+
from strands import Agent
17+
from strands_tools import http_request
18+
19+
os.environ["BYPASS_TOOL_CONSENT"] = "true"
20+
21+
22+
@pytest.fixture
23+
def agent():
24+
"""Create an Agent instance configured with HTTP request tool."""
25+
return Agent(tools=[http_request])
26+
27+
28+
@pytest.fixture(scope="module")
29+
def http_server():
30+
"""Create a local HTTP server for testing."""
31+
host = "localhost"
32+
port = _find_free_port()
33+
server = HTTPServer((host, port), LocalHttpRequestHandler)
34+
35+
thread = threading.Thread(target=server.serve_forever, daemon=True)
36+
thread.start()
37+
38+
if not _wait_for_server(host, port):
39+
server.shutdown()
40+
pytest.fail(f"Server failed to start on {host}:{port}")
41+
42+
server.test_port = port
43+
yield server
44+
45+
server.shutdown()
46+
server.server_close()
47+
48+
49+
def test_get_http_request(agent, http_server):
50+
"""Test GET request functionality using local test server."""
51+
port = http_server.test_port
52+
response = agent(f"Send a GET request to http://localhost:{port}/repo and show me the number of stars")
53+
assert "stars" in str(response).lower() and "99999" in str(response).lower()
54+
55+
56+
def test_post_http_request(agent, http_server):
57+
"""Test POST request functionality using local test server."""
58+
port = http_server.test_port
59+
response = agent(f'Send a POST request to http://localhost:{port} with JSON body {{"foo": "bar"}}')
60+
assert "foo" in str(response).lower() and "bar" in str(response).lower()
61+
62+
63+
def test_update_http_request(agent, http_server):
64+
"""Test PUT request functionality using local test server."""
65+
port = http_server.test_port
66+
response = agent(f'Send a PUT request to http://localhost:{port} with JSON body {{"update": true}}')
67+
assert "update" in str(response).lower() and "true" in str(response).lower()
68+
69+
70+
def test_download_http_request(agent):
71+
"""Verify that the agent can use tool to download a file from a URL"""
72+
response = agent("Download the PNG logo from https://www.python.org/static/community_logos/python-logo.png")
73+
assert "successfully" in str(response).lower() or "image" in str(response).lower(), str(response)
74+
75+
76+
@pytest.mark.skipif("AWS_ACCESS_KEY_ID" not in os.environ, reason="ACCESS_KEY_ID environment variable missing")
77+
def test_list_s3_bucket_http_request(agent):
78+
"""Test AWS S3 bucket listing functionality via HTTP requests."""
79+
region = os.getenv("AWS_REGION", "us-east-1")
80+
response = agent(f"List all S3 buckets in region {region}.")
81+
assert "s3 buckets" in str(response).lower(), str(response)
82+
83+
84+
# --Helper function & Class--
85+
86+
87+
class LocalHttpRequestHandler(BaseHTTPRequestHandler):
88+
"""A simplified HTTP request handler for local testing."""
89+
90+
def _send_json_response(self, status_code: int, payload: dict) -> None:
91+
"""Send a JSON response with the given status and payload."""
92+
self.send_response(status_code)
93+
self.send_header("Content-type", "application/json")
94+
self.end_headers()
95+
self.wfile.write(json.dumps(payload).encode("utf-8"))
96+
97+
def do_GET(self) -> None:
98+
"""Handle GET requests."""
99+
if self.path == "/repo":
100+
response_data = {"name": "sdk-python", "stars": 99999}
101+
self._send_json_response(200, response_data)
102+
else:
103+
self._send_json_response(404, {"error": "Not Found"})
104+
105+
def _handle_request_with_body(self, success_key: str) -> None:
106+
"""Handle requests with a JSON body (for POST, PUT, etc.)."""
107+
try:
108+
content_length = int(self.headers.get("Content-Length", 0))
109+
body = self.rfile.read(content_length)
110+
data = json.loads(body) if body else {}
111+
response_payload = {success_key: data, "status": "success"}
112+
self._send_json_response(200, response_payload)
113+
except json.JSONDecodeError:
114+
self._send_json_response(400, {"error": "Invalid JSON"})
115+
except Exception as e:
116+
self._send_json_response(500, {"error": str(e)})
117+
118+
def do_POST(self) -> None:
119+
"""Handle POST requests by reusing the body handler."""
120+
self._handle_request_with_body("received_data")
121+
122+
def do_PUT(self) -> None:
123+
"""Handle PUT requests by reusing the body handler."""
124+
self._handle_request_with_body("updated_data")
125+
126+
127+
def _find_free_port() -> int:
128+
"""Find and return an available port."""
129+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
130+
s.bind(("", 0))
131+
return s.getsockname()[1]
132+
133+
134+
def _wait_for_server(host: str, port: int, timeout: int = 5) -> bool:
135+
"""Wait for the local test server to become available."""
136+
start_time = time.monotonic()
137+
while time.monotonic() - start_time < timeout:
138+
try:
139+
with socket.create_connection((host, port), timeout=0.1):
140+
return True
141+
except (ConnectionRefusedError, socket.timeout):
142+
time.sleep(0.1)
143+
return False

tests/test_mem0.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
Tests for the memory tool using the Agent interface.
33
"""
44

5+
import builtins
56
import json
67
import os
78
import sys
@@ -336,13 +337,19 @@ def test_invalid_action(mock_opensearch, mock_mem0_client, mock_tool):
336337
@patch.dict(os.environ, {})
337338
def test_missing_opensearch_host(mock_tool):
338339
"""Test missing OpenSearch host defaults to FAISS."""
339-
# Configure the mock_tool
340-
mock_tool.get.side_effect = lambda key, default=None: {"toolUseId": "test-id", "input": {"action": "list"}}.get(
341-
key, default
342-
)
340+
mock_tool.get.side_effect = lambda key, default=None: {
341+
"toolUseId": "test-id",
342+
"input": {"action": "list", "user_id": "test-user"},
343+
}.get(key, default)
344+
345+
real_import = builtins.__import__
346+
347+
def fail_faiss(name, *args, **kwargs):
348+
if name == "faiss":
349+
raise ImportError("No module named 'faiss'")
350+
return real_import(name, *args, **kwargs)
343351

344-
# Mock faiss import
345-
with patch("strands_tools.mem0_memory.faiss", create=True):
352+
with patch("builtins.__import__", side_effect=fail_faiss):
346353
result = mem0_memory.mem0_memory(tool=mock_tool)
347354
assert result["status"] == "error"
348355
assert "The faiss-cpu package is required" in str(result["content"][0]["text"])

0 commit comments

Comments
 (0)