Skip to content

Commit 2394db8

Browse files
committed
Add chat completion and model endpoint tests for MaaS
1 parent 564523d commit 2394db8

File tree

3 files changed

+86
-0
lines changed

3 files changed

+86
-0
lines changed

tests/model_serving/model_server/maas_billing/conftest.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
import pytest
44
import requests
55
from simple_logger.logger import get_logger
6+
from utilities.plugins.constant import OpenAIEnpoints
67

78
from tests.model_serving.model_server.maas_billing.utils import (
89
detect_scheme_via_llmisvc,
910
host_from_ingress_domain,
1011
mint_token,
12+
llmis_name,
1113
)
1214

1315
LOGGER = get_logger(name=__name__)
@@ -44,3 +46,14 @@ def base_url(admin_client) -> str:
4446
scheme = detect_scheme_via_llmisvc(client=admin_client, namespace="llm")
4547
host = host_from_ingress_domain(client=admin_client)
4648
return f"{scheme}://{host}/maas-api"
49+
50+
51+
@pytest.fixture(scope="session")
52+
def model_url(admin_client) -> str:
53+
"""
54+
MODEL_URL:http(s)://<host>/llm/<deployment>/v1/chat/completions
55+
"""
56+
scheme = detect_scheme_via_llmisvc(client=admin_client, namespace="llm")
57+
host = host_from_ingress_domain(client=admin_client)
58+
deployment = llmis_name(client=admin_client, namespace="llm", label_selector=None)
59+
return f"{scheme}://{host}/llm/{deployment}{OpenAIEnpoints.CHAT_COMPLETIONS}"
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
from utilities.plugins.constant import RestHeader, OpenAIEnpoints
2+
from simple_logger.logger import get_logger
3+
4+
LOGGER = get_logger(name=__name__)
5+
MODELS_INFO = OpenAIEnpoints.MODELS_INFO
6+
CHAT_COMPLETIONS = OpenAIEnpoints.CHAT_COMPLETIONS
7+
8+
9+
class TestMaasEndpoints:
10+
def test_model(self, request_session_http, base_url: str, minted_token: str) -> None:
11+
"""Verify /v1/models endpoint is reachable and returns available models."""
12+
headers = {"Authorization": f"Bearer {minted_token}", **RestHeader.HEADERS}
13+
url = f"{base_url}{MODELS_INFO}"
14+
15+
resp = request_session_http.get(url, headers=headers, timeout=60)
16+
assert resp.status_code == 200, f"/v1/models failed: {resp.status_code} {resp.text[:200]}"
17+
18+
body = resp.json()
19+
assert isinstance(body.get("data"), list), "'data' missing or not a list"
20+
assert body["data"], "no models found"
21+
22+
def test_chat_completions(
23+
self,
24+
request_session_http,
25+
base_url: str,
26+
minted_token: str,
27+
model_url: str,
28+
) -> None:
29+
"""
30+
Verify the chat completion endpoint /llm/<deployment>/v1/chat/completions
31+
responds correctly to a prompt request.
32+
33+
"""
34+
headers = {"Authorization": f"Bearer {minted_token}", **RestHeader.HEADERS}
35+
36+
# 1) Pick a model id from /v1/models
37+
models_url = f"{base_url}{MODELS_INFO}"
38+
models_resp = request_session_http.get(models_url, headers=headers, timeout=60)
39+
assert models_resp.status_code == 200, f"/v1/models failed: {models_resp.status_code} {models_resp.text[:200]}"
40+
models = models_resp.json().get("data", [])
41+
assert models, "no models available"
42+
model_id = models[0].get("id", "")
43+
LOGGER.info("Using model_id=%s", model_id)
44+
45+
# 2) Prepare the chat completion endpoint URL
46+
payload = {"model": model_id, "prompt": "Hello", "max_tokens": 50}
47+
LOGGER.info("POST %s with keys=%s", model_url, list(payload.keys()))
48+
resp = request_session_http.post(url=model_url, headers=headers, json=payload, timeout=60)
49+
LOGGER.info("POST %s -> %s", model_url, resp.status_code)
50+
assert resp.status_code == 200, (
51+
f"/v1/chat/completions failed: {resp.status_code} {resp.text[:200]} (url={model_url})"
52+
)
53+
54+
body = resp.json()
55+
assert isinstance(body.get("choices"), list), "'choices' missing or not a list"
56+
if body["choices"]:
57+
msg = body["choices"][0].get("message", {}) or {}
58+
text = msg.get("content") or body["choices"][0].get("text", "")
59+
assert isinstance(text, str) and text.strip() != "", "first choice has no text content"

tests/model_serving/model_server/maas_billing/utils.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,17 @@ def b64url_decode(encoded_str: str) -> bytes:
6565
padding = "=" * (-len(encoded_str) % 4)
6666
padded_bytes = (encoded_str + padding).encode(encoding="utf-8")
6767
return base64.urlsafe_b64decode(s=padded_bytes)
68+
69+
70+
def llmis_name(client, namespace: str = "llm", label_selector: str | None = None) -> str:
71+
"""
72+
Return the name of the first Ready LLMInferenceService.
73+
"""
74+
for service in LLMInferenceService.get(dyn_client=client, namespace=namespace, label_selector=label_selector):
75+
conditions = (service.instance.status or {}).get("conditions", [])
76+
is_ready = any(
77+
condition.get("type") == "Ready" and condition.get("status") == "True" for condition in conditions
78+
)
79+
if is_ready:
80+
return service.name
81+
raise RuntimeError("No Ready LLMInferenceService found")

0 commit comments

Comments
 (0)