Skip to content
This repository was archived by the owner on Jun 5, 2025. It is now read-only.

Commit b3bbc41

Browse files
Add integration tests with muxing
Replicate the current integration tests but instead of using the specific provider URL, e.g. `/ollama` use the muxing URL, i.e. `/v1/mux/`. Muxing functionality should take care of routing the request to the correct model and provider. For the moment we're only going to test with the "catch_all" rule. Meaning, all the requests will be directed to the same model. In future iterations we can expand the integration tests to check for multiple rules across different providers.
1 parent 3e82eba commit b3bbc41

File tree

8 files changed

+250
-17
lines changed

8 files changed

+250
-17
lines changed

tests/integration/anthropic/testcases.yaml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,31 @@ headers:
22
anthropic:
33
x-api-key: ENV_ANTHROPIC_KEY
44

5+
muxing:
6+
mux_url: http://127.0.0.1:8989/v1/mux/
7+
trimm_from_testcase_url: http://127.0.0.1:8989/anthropic/
8+
provider_endpoint:
9+
url: http://127.0.0.1:8989/api/v1/provider-endpoints
10+
headers:
11+
Content-Type: application/json
12+
data: |
13+
{
14+
"name": "anthropic_muxing",
15+
"description": "Muxing testing endpoint",
16+
"provider_type": "anthropic",
17+
"endpoint": "https://api.anthropic.com/",
18+
"auth_type": "api_key",
19+
"api_key": "ENV_ANTHROPIC_KEY"
20+
}
21+
muxes:
22+
url: http://127.0.0.1:8989/api/v1/workspaces/default/muxes
23+
headers:
24+
Content-Type: application/json
25+
rules:
26+
- model: claude-3-5-haiku-20241022
27+
matcher_type: catch_all
28+
matcher: ""
29+
530
testcases:
631
anthropic_chat:
732
name: Anthropic Chat

tests/integration/integration_tests.py

Lines changed: 86 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import asyncio
2+
import copy
23
import json
34
import os
45
import re
56
import sys
6-
from typing import Dict, Optional, Tuple
7+
from typing import Any, Dict, Optional, Tuple
78

89
import requests
910
import structlog
@@ -21,7 +22,7 @@ def __init__(self):
2122
self.failed_tests = [] # Track failed tests
2223

2324
def call_codegate(
24-
self, url: str, headers: dict, data: dict, provider: str
25+
self, url: str, headers: dict, data: dict, provider: str, method: str = "POST"
2526
) -> Optional[requests.Response]:
2627
logger.debug(f"Creating requester for provider: {provider}")
2728
requester = self.requester_factory.create_requester(provider)
@@ -31,12 +32,12 @@ def call_codegate(
3132
logger.debug(f"Headers: {headers}")
3233
logger.debug(f"Data: {data}")
3334

34-
response = requester.make_request(url, headers, data)
35+
response = requester.make_request(url, headers, data, method=method)
3536

3637
# Enhanced response logging
3738
if response is not None:
3839

39-
if response.status_code != 200:
40+
if response.status_code not in [200, 201, 204]:
4041
logger.debug(f"Response error status: {response.status_code}")
4142
logger.debug(f"Response error headers: {dict(response.headers)}")
4243
try:
@@ -174,7 +175,7 @@ async def run_test(self, test: dict, test_headers: dict) -> bool:
174175

175176
async def _get_testcases(
176177
self, testcases_dict: Dict, test_names: Optional[list[str]] = None
177-
) -> Dict:
178+
) -> Dict[str, Dict[str, str]]:
178179
testcases: Dict[str, Dict[str, str]] = testcases_dict["testcases"]
179180

180181
# Filter testcases by provider and test names
@@ -192,24 +193,99 @@ async def _get_testcases(
192193
testcases = filtered_testcases
193194
return testcases
194195

196+
async def _setup_muxing(
197+
self, provider: str, muxing_config: Optional[Dict]
198+
) -> Optional[Tuple[str, str]]:
199+
"""
200+
Muxing setup. Create the provider endpoints and the muxing rules
201+
202+
Return
203+
"""
204+
# The muxing section was not found in the testcases.yaml file. Nothing to do.
205+
if not muxing_config:
206+
return
207+
208+
# Create the provider endpoint
209+
provider_endpoint = muxing_config.get("provider_endpoint")
210+
try:
211+
data_with_api_keys = self.replace_env_variables(provider_endpoint["data"], os.environ)
212+
response_create_provider = self.call_codegate(
213+
provider=provider,
214+
url=provider_endpoint["url"],
215+
headers=provider_endpoint["headers"],
216+
data=json.loads(data_with_api_keys),
217+
)
218+
created_provider_endpoint = response_create_provider.json()
219+
except Exception as e:
220+
logger.warning(f"Could not setup provider endpoint for muxing: {e}")
221+
return
222+
223+
muxes_rules: Dict[str, Any] = muxing_config.get("muxes", {})
224+
try:
225+
# We need to first update all the muxes with the provider_id
226+
for mux in muxes_rules.get("rules", []):
227+
mux["provider_id"] = created_provider_endpoint["id"]
228+
229+
# The endpoint actually takes a list
230+
self.call_codegate(
231+
provider=provider,
232+
url=muxes_rules["url"],
233+
headers=muxes_rules["headers"],
234+
data=muxes_rules.get("rules", []),
235+
method="PUT",
236+
)
237+
except Exception as e:
238+
logger.warning(f"Could not setup muxing rules: {e}")
239+
return
240+
241+
return muxing_config["mux_url"], muxing_config["trimm_from_testcase_url"]
242+
243+
async def _augment_testcases_with_muxing(
244+
self, testcases: Dict, mux_url: str, trimm_from_testcase_url: str
245+
) -> Dict:
246+
"""
247+
Augment the testcases with the muxing information. Copy the testcases
248+
and execute them through the muxing endpoint.
249+
"""
250+
test_cases_with_muxing = copy.deepcopy(testcases)
251+
for test_id, test_data in testcases.items():
252+
# Replace the provider in the URL with the muxed URL
253+
rest_of_path = test_data["url"].replace(trimm_from_testcase_url, "")
254+
new_url = f"{mux_url}{rest_of_path}"
255+
new_test_data = copy.deepcopy(test_data)
256+
new_test_data["url"] = new_url
257+
new_test_id = f"{test_id}_muxed"
258+
test_cases_with_muxing[new_test_id] = new_test_data
259+
260+
return test_cases_with_muxing
261+
195262
async def _setup(
196-
self, testcases_file: str, test_names: Optional[list[str]] = None
263+
self, testcases_file: str, provider: str, test_names: Optional[list[str]] = None
197264
) -> Tuple[Dict, Dict]:
198265
with open(testcases_file, "r") as f:
199-
testcases_dict = yaml.safe_load(f)
266+
testcases_dict: Dict = yaml.safe_load(f)
200267

201268
headers = testcases_dict["headers"]
202269
testcases = await self._get_testcases(testcases_dict, test_names)
203-
return headers, testcases
270+
muxing_result = await self._setup_muxing(provider, testcases_dict.get("muxing", {}))
271+
# We don't have any muxing setup, return the headers and testcases
272+
if not muxing_result:
273+
return headers, testcases
274+
275+
mux_url, trimm_from_testcase_url = muxing_result
276+
test_cases_with_muxing = await self._augment_testcases_with_muxing(
277+
testcases, mux_url, trimm_from_testcase_url
278+
)
279+
280+
return headers, test_cases_with_muxing
204281

205282
async def run_tests(
206283
self,
207284
testcases_file: str,
208285
provider: str,
209286
test_names: Optional[list[str]] = None,
210287
) -> bool:
211-
headers, testcases = await self._setup(testcases_file, test_names)
212-
288+
headers, testcases = await self._setup(testcases_file, provider, test_names)
213289
if not testcases:
214290
logger.warning(
215291
f"No tests found for provider {provider} in file: {testcases_file} "

tests/integration/llamacpp/testcases.yaml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,30 @@ headers:
22
llamacpp:
33
Content-Type: application/json
44

5+
muxing:
6+
mux_url: http://127.0.0.1:8989/v1/mux/
7+
trimm_from_testcase_url: http://127.0.0.1:8989/llamacpp/
8+
provider_endpoint:
9+
url: http://127.0.0.1:8989/api/v1/provider-endpoints
10+
headers:
11+
Content-Type: application/json
12+
data: |
13+
{
14+
"name": "llamacpp_muxing",
15+
"description": "Muxing testing endpoint",
16+
"provider_type": "llamacpp",
17+
"endpoint": "./codegate_volume/models",
18+
"auth_type": "none"
19+
}
20+
muxes:
21+
url: http://127.0.0.1:8989/api/v1/workspaces/default/muxes
22+
headers:
23+
Content-Type: application/json
24+
rules:
25+
- model: qwen2.5-coder-0.5b-instruct-q5_k_m
26+
matcher_type: catch_all
27+
matcher: ""
28+
529
testcases:
630
llamacpp_chat:
731
name: LlamaCPP Chat

tests/integration/ollama/testcases.yaml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,30 @@ headers:
22
ollama:
33
Content-Type: application/json
44

5+
muxing:
6+
mux_url: http://127.0.0.1:8989/v1/mux/
7+
trimm_from_testcase_url: http://127.0.0.1:8989/ollama/
8+
provider_endpoint:
9+
url: http://127.0.0.1:8989/api/v1/provider-endpoints
10+
headers:
11+
Content-Type: application/json
12+
data: |
13+
{
14+
"name": "ollama_muxing",
15+
"description": "Muxing testing endpoint",
16+
"provider_type": "ollama",
17+
"endpoint": "http://127.0.0.1:11434",
18+
"auth_type": "none"
19+
}
20+
muxes:
21+
url: http://127.0.0.1:8989/api/v1/workspaces/default/muxes
22+
headers:
23+
Content-Type: application/json
24+
rules:
25+
- model: qwen2.5-coder:1.5b
26+
matcher_type: catch_all
27+
matcher: ""
28+
529
testcases:
630
ollama_chat:
731
name: Ollama Chat

tests/integration/openai/testcases.yaml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,31 @@ headers:
22
openai:
33
Authorization: Bearer ENV_OPENAI_KEY
44

5+
muxing:
6+
mux_url: http://127.0.0.1:8989/v1/mux/
7+
trimm_from_testcase_url: http://127.0.0.1:8989/openai/
8+
provider_endpoint:
9+
url: http://127.0.0.1:8989/api/v1/provider-endpoints
10+
headers:
11+
Content-Type: application/json
12+
data: |
13+
{
14+
"name": "openai_muxing",
15+
"description": "Muxing testing endpoint",
16+
"provider_type": "openai",
17+
"endpoint": "https://api.openai.com/",
18+
"auth_type": "api_key",
19+
"api_key": "ENV_OPENAI_KEY"
20+
}
21+
muxes:
22+
url: http://127.0.0.1:8989/api/v1/workspaces/default/muxes
23+
headers:
24+
Content-Type: application/json
25+
rules:
26+
- model: gpt-4o-mini
27+
matcher_type: catch_all
28+
matcher: ""
29+
530
testcases:
631
openai_chat:
732
name: OpenAI Chat

tests/integration/openrouter/testcases.yaml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,31 @@ headers:
22
openrouter:
33
Authorization: Bearer ENV_OPENROUTER_KEY
44

5+
muxing:
6+
mux_url: http://127.0.0.1:8989/v1/mux/
7+
trimm_from_testcase_url: http://localhost:8989/openrouter/
8+
provider_endpoint:
9+
url: http://127.0.0.1:8989/api/v1/provider-endpoints
10+
headers:
11+
Content-Type: application/json
12+
data: |
13+
{
14+
"name": "openrouter_muxing",
15+
"description": "Muxing testing endpoint",
16+
"provider_type": "openrouter",
17+
"endpoint": "https://openrouter.ai/api",
18+
"auth_type": "api_key",
19+
"api_key": "ENV_OPENROUTER_KEY"
20+
}
21+
muxes:
22+
url: http://127.0.0.1:8989/api/v1/workspaces/default/muxes
23+
headers:
24+
Content-Type: application/json
25+
rules:
26+
- model: anthropic/claude-3-5-haiku
27+
matcher_type: catch_all
28+
matcher: ""
29+
530
testcases:
631
anthropic_chat:
732
name: Openrouter Chat

tests/integration/requesters.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,33 +11,43 @@
1111

1212
class BaseRequester(ABC):
1313
@abstractmethod
14-
def make_request(self, url: str, headers: dict, data: dict) -> Optional[requests.Response]:
14+
def make_request(
15+
self, url: str, headers: dict, data: dict, method: str = "POST"
16+
) -> Optional[requests.Response]:
1517
pass
1618

1719

1820
class StandardRequester(BaseRequester):
19-
def make_request(self, url: str, headers: dict, data: dict) -> Optional[requests.Response]:
21+
def make_request(
22+
self, url: str, headers: dict, data: dict, method: str = "POST"
23+
) -> Optional[requests.Response]:
2024
# Ensure Content-Type is always set correctly
2125
headers["Content-Type"] = "application/json"
2226

2327
# Explicitly serialize to JSON string
2428
json_data = json.dumps(data)
2529

26-
return requests.post(
27-
url, headers=headers, data=json_data # Use data instead of json parameter
30+
return requests.request(
31+
method=method,
32+
url=url,
33+
headers=headers,
34+
data=json_data, # Use data instead of json parameter
2835
)
2936

3037

3138
class CopilotRequester(BaseRequester):
32-
def make_request(self, url: str, headers: dict, data: dict) -> Optional[requests.Response]:
39+
def make_request(
40+
self, url: str, headers: dict, data: dict, method: str = "POST"
41+
) -> Optional[requests.Response]:
3342
# Ensure Content-Type is always set correctly
3443
headers["Content-Type"] = "application/json"
3544

3645
# Explicitly serialize to JSON string
3746
json_data = json.dumps(data)
3847

39-
return requests.post(
40-
url,
48+
return requests.request(
49+
method=method,
50+
url=url,
4151
data=json_data, # Use data instead of json parameter
4252
headers=headers,
4353
proxies={"https": "https://localhost:8990", "http": "http://localhost:8990"},

tests/integration/vllm/testcases.yaml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,30 @@ headers:
22
vllm:
33
Content-Type: application/json
44

5+
muxing:
6+
mux_url: http://127.0.0.1:8989/v1/mux/
7+
trimm_from_testcase_url: http://127.0.0.1:8989/vllm/
8+
provider_endpoint:
9+
url: http://127.0.0.1:8989/api/v1/provider-endpoints
10+
headers:
11+
Content-Type: application/json
12+
data: |
13+
{
14+
"name": "vllm_muxing",
15+
"description": "Muxing testing endpoint",
16+
"provider_type": "vllm",
17+
"endpoint": "http://127.0.0.1:8000",
18+
"auth_type": "none"
19+
}
20+
muxes:
21+
url: http://127.0.0.1:8989/api/v1/workspaces/default/muxes
22+
headers:
23+
Content-Type: application/json
24+
rules:
25+
- model: Qwen/Qwen2.5-Coder-0.5B-Instruct
26+
matcher_type: catch_all
27+
matcher: ""
28+
529
testcases:
630
vllm_chat:
731
name: VLLM Chat

0 commit comments

Comments
 (0)