main
py 103 lines 3.73 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 """Non-object JSON bodies must fail validation cleanly (4xx), not AttributeError (500).
5
6 mode=before validators that call data.get(...) without an isinstance(data, dict)
7 guard raise AttributeError for string/list/scalar bodies and surface as HTTP 500.
8
9 This extends the chat completion coverage added in #51654 to the remaining
10 request models whose before-validators were missing the same guard.
11 """
12
13 import pytest
14 from pydantic import ValidationError
15
16 from vllm.entrypoints.openai.chat_completion.protocol import ChatCompletionRequest
17 from vllm.entrypoints.openai.completion.protocol import CompletionRequest
18 from vllm.entrypoints.openai.responses.protocol import ResponsesRequest
19 from vllm.entrypoints.pooling.classify.protocol import ClassificationChatRequest
20 from vllm.entrypoints.pooling.embed.protocol import EmbeddingChatRequest
21 from vllm.entrypoints.pooling.pooling.protocol import PoolingChatRequest
22 from vllm.entrypoints.scale_out.token_in_token_out.protocol import GenerateRequest
23 from vllm.entrypoints.serve.tokenize.protocol import TokenizeChatRequest
24 from vllm.entrypoints.speech_to_text.transcription.protocol import TranscriptionRequest
25 from vllm.entrypoints.speech_to_text.translation.protocol import TranslationRequest
26 from vllm.exceptions import VLLMValidationError
27
28 pytestmark = pytest.mark.skip_global_cleanup
29
30 REQUEST_MODELS = [
31 CompletionRequest,
32 ResponsesRequest,
33 EmbeddingChatRequest,
34 ClassificationChatRequest,
35 PoolingChatRequest,
36 TokenizeChatRequest,
37 TranscriptionRequest,
38 TranslationRequest,
39 ]
40
41
42 @pytest.mark.parametrize("request_model", REQUEST_MODELS, ids=lambda m: m.__name__)
43 @pytest.mark.parametrize(
44 "payload",
45 [
46 "this is not valid json{{{",
47 ["not", "an", "object"],
48 42,
49 None,
50 True,
51 ],
52 )
53 def test_request_models_reject_non_object_body(request_model, payload):
54 with pytest.raises(ValidationError):
55 request_model.model_validate(payload)
56
57
58 def test_completion_request_still_validates_dict_bodies():
59 """The guard must not swallow real field-level errors on object bodies."""
60 with pytest.raises(VLLMValidationError, match="prompt"):
61 CompletionRequest.model_validate({"model": "qwen", "prompt": ""})
62
63
64 def test_tokenize_chat_request_still_validates_dict_bodies():
65 with pytest.raises(VLLMValidationError, match="add_generation_prompt"):
66 TokenizeChatRequest.model_validate(
67 {
68 "model": "qwen",
69 "messages": [{"role": "user", "content": "hello"}],
70 "continue_final_message": True,
71 "add_generation_prompt": True,
72 }
73 )
74
75
76 CACHE_SALT_MODELS = [
77 (CompletionRequest, {"prompt": "hello"}),
78 (ChatCompletionRequest, {"messages": [{"role": "user", "content": "hello"}]}),
79 (ResponsesRequest, {"input": "hello"}),
80 (GenerateRequest, {"token_ids": [1], "sampling_params": {}}),
81 ]
82
83
84 @pytest.mark.parametrize(
85 ("request_model", "payload"),
86 CACHE_SALT_MODELS,
87 ids=[m.__name__ for m, _ in CACHE_SALT_MODELS],
88 )
89 def test_request_models_reject_forbidden_cache_salt(request_model, payload):
90 with pytest.raises(VLLMValidationError, match="cache_salt"):
91 request_model.model_validate({**payload, "cache_salt": "/"})
92
93
94 def test_request_models_reject_overlong_cache_salt():
95 with pytest.raises(VLLMValidationError, match="cache_salt"):
96 CompletionRequest.model_validate({"prompt": "hello", "cache_salt": "a" * 129})
97
98
99 def test_request_models_accept_safe_cache_salt():
100 request = CompletionRequest.model_validate(
101 {"prompt": "hello", "cache_salt": "safe-cache_salt-123"}
102 )
103 assert request.cache_salt == "safe-cache_salt-123"