[Test] Match invalid realtime requests to the current endpoint contract
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
This commit is contained in:
1 parent
ee8fdab1dc
commit
0dbee5684b
2 files changed
+74
-37
No files matched your search
@@ -216,44 +216,40 @@ def test_video_chat_stream_invalid_requests(
|
|||||||
# WS /v1/realtime
|
# WS /v1/realtime
|
||||||
# ─────────────────────────────────────────────────────────────────────────────
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
# Resolved in ``test_realtime_invalid_requests`` (skip ``session.created`` handshake).
|
|
||||||
_REALTIME_WS_MODEL_MISMATCH = object()
|
|
||||||
_REALTIME_WS_INVALID_AUDIO_APPEND = object()
|
|
||||||
|
|
||||||
|
|
||||||
@hardware_test(res={"cuda": "H100", "rocm": "MI325"}, num_cards=2)
|
@hardware_test(res={"cuda": "H100", "rocm": "MI325"}, num_cards=2)
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"send_frames_spec, ws_error_code, err_message",
|
"send_frames_spec, ws_error_code, err_message",
|
||||||
[
|
[
|
||||||
pytest.param("{not-json", "invalid_json", "Invalid JSON", id="invalid_json"),
|
pytest.param("{not-json", "invalid_event", "Invalid or unrecognized client event", id="invalid_json"),
|
||||||
pytest.param(
|
pytest.param(
|
||||||
json.dumps({"type": "session.update"}),
|
json.dumps({"type": "session.update"}),
|
||||||
"invalid_event",
|
"invalid_event",
|
||||||
("model", "Missing required field"),
|
"Invalid or unrecognized client event",
|
||||||
id="session_update_missing_model",
|
id="session_update_missing_session",
|
||||||
),
|
),
|
||||||
pytest.param(
|
pytest.param(
|
||||||
_REALTIME_WS_MODEL_MISMATCH,
|
json.dumps({"type": "session.update", "session": []}),
|
||||||
"model_not_found",
|
"invalid_event",
|
||||||
("model", "does not exist"),
|
"Invalid or unrecognized client event",
|
||||||
id="session_update_model_mismatch",
|
id="session_update_invalid_session",
|
||||||
),
|
),
|
||||||
pytest.param(
|
pytest.param(
|
||||||
json.dumps({"type": "input_audio_buffer.commit", "final": False}),
|
json.dumps({"type": "input_audio_buffer.commit"}),
|
||||||
"model_not_validated",
|
"invalid_request_error",
|
||||||
("session.update", "validate the model"),
|
"Input audio buffer is empty",
|
||||||
id="commit_without_model_validation",
|
id="commit_without_audio",
|
||||||
),
|
),
|
||||||
pytest.param(
|
pytest.param(
|
||||||
json.dumps({"type": "unknown.event"}),
|
json.dumps({"type": "unknown.event"}),
|
||||||
"unknown_event",
|
"invalid_event",
|
||||||
"Unknown event type",
|
"Invalid or unrecognized client event",
|
||||||
id="unknown_event_type",
|
id="unknown_event_type",
|
||||||
),
|
),
|
||||||
pytest.param(
|
pytest.param(
|
||||||
_REALTIME_WS_INVALID_AUDIO_APPEND,
|
json.dumps({"type": "input_audio_buffer.append", "audio": "not-valid-base64!!!"}),
|
||||||
"invalid_audio",
|
"invalid_request_error",
|
||||||
"Invalid audio data",
|
"Invalid base64 audio data",
|
||||||
id="invalid_audio_append",
|
id="invalid_audio_append",
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -266,27 +262,12 @@ def test_realtime_invalid_requests(
|
|||||||
ws_error_code: str,
|
ws_error_code: str,
|
||||||
err_message: str | tuple[str, ...],
|
err_message: str | tuple[str, ...],
|
||||||
) -> None:
|
) -> None:
|
||||||
send_frames: Any
|
|
||||||
if send_frames_spec is _REALTIME_WS_MODEL_MISMATCH:
|
|
||||||
send_frames = json.dumps(
|
|
||||||
{
|
|
||||||
"type": "session.update",
|
|
||||||
"model": "this-model-is-not-served-on-this-instance",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
elif send_frames_spec is _REALTIME_WS_INVALID_AUDIO_APPEND:
|
|
||||||
send_frames = [
|
|
||||||
json.dumps({"type": "session.update", "model": omni_server.model}),
|
|
||||||
json.dumps({"type": "input_audio_buffer.append", "audio": "not-valid-base64!!!"}),
|
|
||||||
]
|
|
||||||
else:
|
|
||||||
send_frames = send_frames_spec
|
|
||||||
online_client.send_realtime_ws_request(
|
online_client.send_realtime_ws_request(
|
||||||
{
|
{
|
||||||
"send_frames": send_frames,
|
"send_frames": send_frames_spec,
|
||||||
"timeout": 120,
|
"timeout": 120,
|
||||||
"ws_max_size": None,
|
"ws_max_size": None,
|
||||||
"ws_skip_types": ["session.created"],
|
"ws_skip_types": ["session.created", "conversation.created"],
|
||||||
"ws_json_type": "error",
|
"ws_json_type": "error",
|
||||||
"ws_error_code": ws_error_code,
|
"ws_error_code": ws_error_code,
|
||||||
"err_message": err_message,
|
"err_message": err_message,
|
||||||
|
|||||||
@@ -4,7 +4,10 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
import base64
|
import base64
|
||||||
|
import json
|
||||||
|
from types import SimpleNamespace
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pytest
|
import pytest
|
||||||
@@ -84,3 +87,56 @@ class TestAsyncOmniStreamingParamsValidation:
|
|||||||
p = SamplingParams(n=1, stop=["\n"], output_kind=RequestOutputKind.DELTA)
|
p = SamplingParams(n=1, stop=["\n"], output_kind=RequestOutputKind.DELTA)
|
||||||
with pytest.raises(ValueError, match="Input streaming"):
|
with pytest.raises(ValueError, match="Input streaming"):
|
||||||
AsyncOmni._validate_streaming_input_sampling_params(p)
|
AsyncOmni._validate_streaming_input_sampling_params(p)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"frame,code,message",
|
||||||
|
[
|
||||||
|
("{not-json", "invalid_event", "Invalid or unrecognized client event"),
|
||||||
|
(json.dumps({"type": "session.update"}), "invalid_event", "Invalid or unrecognized client event"),
|
||||||
|
(
|
||||||
|
json.dumps({"type": "session.update", "session": []}),
|
||||||
|
"invalid_event",
|
||||||
|
"Invalid or unrecognized client event",
|
||||||
|
),
|
||||||
|
(json.dumps({"type": "input_audio_buffer.commit"}), "invalid_request_error", "Input audio buffer is empty"),
|
||||||
|
(json.dumps({"type": "unknown.event"}), "invalid_event", "Invalid or unrecognized client event"),
|
||||||
|
(
|
||||||
|
json.dumps({"type": "input_audio_buffer.append", "audio": "not-valid-base64!!!"}),
|
||||||
|
"invalid_request_error",
|
||||||
|
"Invalid base64 audio data",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_full_duplex_invalid_request_contract(frame, code, message):
|
||||||
|
from starlette.websockets import WebSocketDisconnect
|
||||||
|
|
||||||
|
from vllm_omni.entrypoints.openai.realtime.connection import OpenAIFullDuplexConnection
|
||||||
|
|
||||||
|
class Socket:
|
||||||
|
def __init__(self):
|
||||||
|
self.sent = []
|
||||||
|
self.frames = iter([frame])
|
||||||
|
|
||||||
|
async def accept(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def receive_text(self):
|
||||||
|
value = next(self.frames, None)
|
||||||
|
if value is None:
|
||||||
|
raise WebSocketDisconnect()
|
||||||
|
return value
|
||||||
|
|
||||||
|
async def send_text(self, payload):
|
||||||
|
self.sent.append(json.loads(payload))
|
||||||
|
|
||||||
|
async def run():
|
||||||
|
socket = Socket()
|
||||||
|
connection = OpenAIFullDuplexConnection(socket, SimpleNamespace(), "test-model", None)
|
||||||
|
await connection.handle_connection()
|
||||||
|
assert [event["type"] for event in socket.sent] == ["session.created", "conversation.created", "error"]
|
||||||
|
error = socket.sent[-1]["error"]
|
||||||
|
assert error["code"] == code
|
||||||
|
assert error["message"] == message
|
||||||
|
|
||||||
|
asyncio.run(run())
|
||||||
Reference in new issue
Block a user