Skip to content

Commit 4218c9d

Browse files
committed
refactor(test): update box integration tests to use proxy interface
1 parent 0625ad5 commit 4218c9d

File tree

1 file changed

+289
-32
lines changed

1 file changed

+289
-32
lines changed

tests/integration/test_box_integration.py

Lines changed: 289 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,294 @@
1+
"""
2+
Integration tests for Box API via mcp-openapi-proxy, FastMCP mode.
3+
Requires BOX_API_KEY in .env to run.
4+
"""
5+
16
import os
7+
import json
28
import pytest
3-
import requests
9+
from dotenv import load_dotenv
10+
from mcp_openapi_proxy.utils import fetch_openapi_spec
11+
from mcp_openapi_proxy.server_fastmcp import list_functions, call_function
12+
13+
# Load .env file from project root if it exists
14+
load_dotenv(dotenv_path=os.path.join(os.path.dirname(__file__), '../../.env'))
415

16+
# --- Configuration ---
517
BOX_API_KEY = os.getenv("BOX_API_KEY")
18+
# Use the spec from APIs.guru directory
19+
SPEC_URL = "https://raw.githubusercontent.com/APIs-guru/openapi-directory/main/APIs/box.com/2.0.0/openapi.yaml"
20+
# Whitelist the endpoints needed for these tests
21+
TOOL_WHITELIST = "/folders/{folder_id},/recent_items,/folders/{folder_id}/items" # Added /folders/{folder_id}/items
22+
TOOL_PREFIX = "box_"
23+
# Box API uses Bearer token auth
24+
API_AUTH_TYPE = "Bearer"
25+
# Box API base URL (though the spec should define this)
26+
SERVER_URL_OVERRIDE = "https://api.box.com/2.0"
27+
28+
# --- Helper Function ---
29+
def get_tool_name(tools, original_name):
30+
"""Find tool name by original endpoint name (e.g., 'GET /path')."""
31+
# Ensure tools is a list of dictionaries
32+
if not isinstance(tools, list) or not all(isinstance(t, dict) for t in tools):
33+
print(f"DEBUG: Invalid tools structure: {tools}")
34+
return None
35+
# Find the tool matching the original name (method + path)
36+
tool = next((t for t in tools if t.get("original_name") == original_name), None)
37+
if not tool:
38+
print(f"DEBUG: Tool not found for {original_name}. Available tools: {[t.get('original_name', 'N/A') for t in tools]}")
39+
return tool.get("name") if tool else None
40+
41+
# --- Pytest Fixture ---
42+
@pytest.fixture
43+
def box_setup(reset_env_and_module):
44+
"""Fixture to set up Box env and list functions."""
45+
env_key = reset_env_and_module
46+
# Corrected line 46: Concatenate "..." within the expression
47+
print(f"DEBUG: BOX_API_KEY: {(BOX_API_KEY[:5] + '...') if BOX_API_KEY else 'Not set'}")
48+
if not BOX_API_KEY or "your_key" in BOX_API_KEY.lower():
49+
print("DEBUG: Skipping due to missing or placeholder BOX_API_KEY")
50+
pytest.skip("BOX_API_KEY missing or placeholder—please set it in .env!")
51+
52+
# Set environment variables for the proxy
53+
os.environ[env_key] = SPEC_URL
54+
os.environ["API_KEY"] = BOX_API_KEY
55+
os.environ["API_AUTH_TYPE"] = API_AUTH_TYPE
56+
os.environ["TOOL_WHITELIST"] = TOOL_WHITELIST
57+
os.environ["TOOL_NAME_PREFIX"] = TOOL_PREFIX
58+
os.environ["SERVER_URL_OVERRIDE"] = SERVER_URL_OVERRIDE # Ensure proxy uses correct base URL
59+
os.environ["DEBUG"] = "true"
60+
print(f"DEBUG: API_KEY set for proxy: {os.environ['API_KEY'][:5]}...")
61+
62+
print(f"DEBUG: Fetching spec from {SPEC_URL}")
63+
spec = fetch_openapi_spec(SPEC_URL)
64+
assert spec, f"Failed to fetch spec from {SPEC_URL}"
65+
66+
print("DEBUG: Listing available functions via proxy")
67+
tools_json = list_functions(env_key=env_key)
68+
tools = json.loads(tools_json)
69+
print(f"DEBUG: Tools listed by proxy: {tools_json}")
70+
assert tools, "No functions generated by proxy"
71+
assert isinstance(tools, list), "Generated functions should be a list"
72+
73+
return env_key, tools
74+
75+
# --- Test Functions ---
76+
@pytest.mark.integration
77+
def test_box_get_folder_info(box_setup):
78+
"""Test getting folder info via the proxy."""
79+
env_key, tools = box_setup
80+
folder_id = "0" # Root folder ID
81+
original_name = "GET /folders/{folder_id}" # Use the actual path template
82+
83+
# Find the normalized tool name
84+
tool_name = get_tool_name(tools, original_name)
85+
assert tool_name, f"Tool for {original_name} not found!"
86+
print(f"DEBUG: Found tool name: {tool_name}")
87+
88+
print(f"DEBUG: Calling proxy function {tool_name} for folder_id={folder_id}")
89+
response_json_str = call_function(
90+
function_name=tool_name,
91+
parameters={"folder_id": folder_id},
92+
env_key=env_key
93+
)
94+
print(f"DEBUG: Raw response string from proxy: {response_json_str}")
95+
# --- Add size debugging ---
96+
response_size_bytes = len(response_json_str.encode('utf-8'))
97+
print(f"DEBUG: Raw response size from proxy (get_folder_info): {response_size_bytes} bytes ({len(response_json_str)} chars)")
98+
# --- End size debugging ---
99+
100+
try:
101+
# The proxy returns the API response as a JSON string, parse it
102+
response_data = json.loads(response_json_str)
103+
104+
# Check for API errors returned via the proxy
105+
if isinstance(response_data, dict) and "error" in response_data:
106+
print(f"DEBUG: Error received from proxy/API: {response_data['error']}")
107+
if "401" in response_data["error"] or "invalid_token" in response_data["error"]:
108+
assert False, "BOX_API_KEY is invalid—please check your token!"
109+
assert False, f"Box API returned an error via proxy: {response_json_str}"
110+
111+
# Assertions on the actual Box API response data
112+
assert isinstance(response_data, dict), f"Parsed response is not a dictionary: {response_data}"
113+
assert "id" in response_data and response_data["id"] == folder_id, f"Folder ID mismatch or missing: {response_data}"
114+
assert "name" in response_data, f"Folder name missing: {response_data}"
115+
assert response_data.get("type") == "folder", f"Incorrect type: {response_data}"
116+
print(f"DEBUG: Successfully got info for folder: {response_data.get('name')}")
117+
118+
except json.JSONDecodeError:
119+
assert False, f"Response from proxy is not valid JSON: {response_json_str}"
120+
121+
@pytest.mark.integration
122+
def test_box_list_folder_contents(box_setup):
123+
"""Test listing folder contents via the proxy (using the same GET /folders/{id} endpoint)."""
124+
env_key, tools = box_setup
125+
folder_id = "0" # Root folder ID
126+
original_name = "GET /folders/{folder_id}" # Use the actual path template
127+
128+
# Find the normalized tool name (same as the previous test)
129+
tool_name = get_tool_name(tools, original_name)
130+
assert tool_name, f"Tool for {original_name} not found!"
131+
print(f"DEBUG: Found tool name: {tool_name}")
132+
133+
print(f"DEBUG: Calling proxy function {tool_name} for folder_id={folder_id}")
134+
response_json_str = call_function(
135+
function_name=tool_name,
136+
parameters={"folder_id": folder_id},
137+
env_key=env_key
138+
)
139+
print(f"DEBUG: Raw response string from proxy: {response_json_str}")
140+
# --- Add size debugging ---
141+
response_size_bytes = len(response_json_str.encode('utf-8'))
142+
print(f"DEBUG: Raw response size from proxy (list_folder_contents): {response_size_bytes} bytes ({len(response_json_str)} chars)")
143+
# --- End size debugging ---
144+
145+
try:
146+
# Parse the JSON string response from the proxy
147+
response_data = json.loads(response_json_str)
148+
149+
# Check for API errors
150+
if isinstance(response_data, dict) and "error" in response_data:
151+
print(f"DEBUG: Error received from proxy/API: {response_data['error']}")
152+
if "401" in response_data["error"] or "invalid_token" in response_data["error"]:
153+
assert False, "BOX_API_KEY is invalid—please check your token!"
154+
assert False, f"Box API returned an error via proxy: {response_json_str}"
155+
156+
# Assertions on the Box API response structure for folder contents
157+
assert isinstance(response_data, dict), f"Parsed response is not a dictionary: {response_data}"
158+
assert "item_collection" in response_data, f"Key 'item_collection' missing in response: {response_data}"
159+
entries = response_data["item_collection"].get("entries")
160+
assert isinstance(entries, list), f"'entries' is not a list or missing: {response_data.get('item_collection')}"
161+
162+
# Print the contents for verification during test run
163+
print("\nBox root folder contents (via proxy):")
164+
for entry in entries:
165+
print(f" {entry.get('type', 'N/A')}: {entry.get('name', 'N/A')} (id: {entry.get('id', 'N/A')})")
166+
167+
# Optionally check structure of at least one entry if list is not empty
168+
if entries:
169+
entry = entries[0]
170+
assert "type" in entry
171+
assert "id" in entry
172+
assert "name" in entry
173+
print(f"DEBUG: Successfully listed {len(entries)} items in root folder.")
174+
175+
except json.JSONDecodeError:
176+
assert False, f"Response from proxy is not valid JSON: {response_json_str}"
177+
178+
@pytest.mark.integration
179+
def test_box_get_recent_items(box_setup):
180+
"""Test getting recent items via the proxy."""
181+
env_key, tools = box_setup
182+
original_name = "GET /recent_items"
183+
184+
# Find the normalized tool name
185+
tool_name = get_tool_name(tools, original_name)
186+
assert tool_name, f"Tool for {original_name} not found!"
187+
print(f"DEBUG: Found tool name: {tool_name}")
188+
189+
print(f"DEBUG: Calling proxy function {tool_name} for recent items")
190+
# No parameters needed for the basic call
191+
response_json_str = call_function(
192+
function_name=tool_name,
193+
parameters={},
194+
env_key=env_key
195+
)
196+
print(f"DEBUG: Raw response string from proxy: {response_json_str}")
197+
# --- Add size debugging ---
198+
response_size_bytes = len(response_json_str.encode('utf-8'))
199+
print(f"DEBUG: Raw response size from proxy (get_recent_items): {response_size_bytes} bytes ({len(response_json_str)} chars)")
200+
# --- End size debugging ---
201+
202+
try:
203+
# Parse the JSON string response from the proxy
204+
response_data = json.loads(response_json_str)
205+
206+
# Check for API errors
207+
if isinstance(response_data, dict) and "error" in response_data:
208+
print(f"DEBUG: Error received from proxy/API: {response_data['error']}")
209+
if "401" in response_data["error"] or "invalid_token" in response_data["error"]:
210+
assert False, "BOX_API_KEY is invalid—please check your token!"
211+
assert False, f"Box API returned an error via proxy: {response_json_str}"
212+
213+
# Assertions on the Box API response structure for recent items
214+
assert isinstance(response_data, dict), f"Parsed response is not a dictionary: {response_data}"
215+
assert "entries" in response_data, f"Key 'entries' missing in response: {response_data}"
216+
entries = response_data["entries"]
217+
assert isinstance(entries, list), f"'entries' is not a list: {entries}"
218+
219+
# Print the recent items for verification
220+
print("\nBox recent items (via proxy):")
221+
for entry in entries[:5]: # Print first 5 for brevity
222+
item = entry.get("item", {})
223+
print(f" {entry.get('type', 'N/A')} - {item.get('type', 'N/A')}: {item.get('name', 'N/A')} (id: {item.get('id', 'N/A')})")
224+
225+
# Optionally check structure of at least one entry if list is not empty
226+
if entries:
227+
entry = entries[0]
228+
assert "type" in entry
229+
assert "item" in entry and isinstance(entry["item"], dict)
230+
assert "id" in entry["item"]
231+
assert "name" in entry["item"]
232+
print(f"DEBUG: Successfully listed {len(entries)} recent items.")
233+
234+
except json.JSONDecodeError:
235+
assert False, f"Response from proxy is not valid JSON: {response_json_str}"
236+
237+
@pytest.mark.integration
238+
def test_box_list_folder_items_endpoint(box_setup):
239+
"""Test listing folder items via the dedicated /folders/{id}/items endpoint."""
240+
env_key, tools = box_setup
241+
folder_id = "0" # Root folder ID
242+
original_name = "GET /folders/{folder_id}/items" # The specific items endpoint
243+
244+
# Find the normalized tool name
245+
tool_name = get_tool_name(tools, original_name)
246+
assert tool_name, f"Tool for {original_name} not found!"
247+
print(f"DEBUG: Found tool name: {tool_name}")
248+
249+
print(f"DEBUG: Calling proxy function {tool_name} for folder_id={folder_id}")
250+
response_json_str = call_function(
251+
function_name=tool_name,
252+
parameters={"folder_id": folder_id}, # Pass folder_id parameter
253+
env_key=env_key
254+
)
255+
print(f"DEBUG: Raw response string from proxy: {response_json_str}")
256+
# --- Add size debugging ---
257+
response_size_bytes = len(response_json_str.encode('utf-8'))
258+
print(f"DEBUG: Raw response size from proxy (list_folder_items_endpoint): {response_size_bytes} bytes ({len(response_json_str)} chars)")
259+
# --- End size debugging ---
260+
261+
try:
262+
# Parse the JSON string response from the proxy
263+
response_data = json.loads(response_json_str)
264+
265+
# Check for API errors
266+
if isinstance(response_data, dict) and "error" in response_data:
267+
print(f"DEBUG: Error received from proxy/API: {response_data['error']}")
268+
if "401" in response_data["error"] or "invalid_token" in response_data["error"]:
269+
assert False, "BOX_API_KEY is invalid—please check your token!"
270+
assert False, f"Box API returned an error via proxy: {response_json_str}"
271+
272+
# Assertions on the Box API response structure for listing items
273+
assert isinstance(response_data, dict), f"Parsed response is not a dictionary: {response_data}"
274+
assert "entries" in response_data, f"Key 'entries' missing in response: {response_data}"
275+
entries = response_data["entries"]
276+
assert isinstance(entries, list), f"'entries' is not a list: {entries}"
277+
assert "total_count" in response_data, f"Key 'total_count' missing: {response_data}"
278+
279+
# Print the items for verification
280+
print(f"\nBox folder items (via {original_name} endpoint):")
281+
for entry in entries:
282+
print(f" {entry.get('type', 'N/A')}: {entry.get('name', 'N/A')} (id: {entry.get('id', 'N/A')})")
283+
284+
# Optionally check structure of at least one entry if list is not empty
285+
if entries:
286+
entry = entries[0]
287+
assert "type" in entry
288+
assert "id" in entry
289+
assert "name" in entry
290+
print(f"DEBUG: Successfully listed {len(entries)} items (total_count: {response_data['total_count']}) using {original_name}.")
291+
292+
except json.JSONDecodeError:
293+
assert False, f"Response from proxy is not valid JSON: {response_json_str}"
6294

7-
@pytest.mark.skipif(not BOX_API_KEY, reason="No BOX_API_KEY set in environment.")
8-
def test_box_get_folder_info():
9-
folder_id = "0" # Root folder
10-
headers = {"Authorization": f"Bearer {BOX_API_KEY}"}
11-
resp = requests.get(f"https://api.box.com/2.0/folders/{folder_id}", headers=headers)
12-
assert resp.status_code == 200
13-
data = resp.json()
14-
assert "id" in data and data["id"] == folder_id
15-
assert "name" in data
16-
assert data["type"] == "folder"
17-
18-
@pytest.mark.skipif(not BOX_API_KEY, reason="No BOX_API_KEY set in environment.")
19-
def test_box_list_files_and_folders():
20-
folder_id = "0" # Root folder
21-
headers = {"Authorization": f"Bearer {BOX_API_KEY}"}
22-
resp = requests.get(f"https://api.box.com/2.0/folders/{folder_id}", headers=headers)
23-
assert resp.status_code == 200
24-
data = resp.json()
25-
assert "item_collection" in data
26-
entries = data["item_collection"]["entries"]
27-
assert isinstance(entries, list)
28-
# Print the filenames/types for debug
29-
print("\nBox root folder contents:")
30-
for entry in entries:
31-
print(f" {entry['type']}: {entry['name']} (id: {entry['id']})")
32-
# Optionally check at least one entry
33-
if entries:
34-
entry = entries[0]
35-
assert "type" in entry
36-
assert "id" in entry
37-
assert "name" in entry

0 commit comments

Comments
 (0)