1515def wait_for_complete_response (context : Context ) -> None :
1616 """Wait for the response to be complete."""
1717 context .response_data = _parse_streaming_response (context .response .text )
18- print (context .response_data )
1918 context .response .raise_for_status ()
2019 assert context .response_data ["finished" ] is True
2120
@@ -31,10 +30,21 @@ def ask_question(context: Context, endpoint: str) -> None:
3130 json_str = replace_placeholders (context , context .text or "{}" )
3231
3332 data = json .loads (json_str )
34- print (f"Request data: { data } " )
3533 context .response = requests .post (url , json = data , timeout = DEFAULT_LLM_TIMEOUT )
3634
3735
36+ def _read_streamed_response (response : requests .Response ) -> str :
37+ """Read a streaming response body, tolerating premature close (e.g. after error event)."""
38+ chunks = []
39+ try :
40+ for line in response .iter_lines (decode_unicode = True ):
41+ if line is not None :
42+ chunks .append (line + "\n " )
43+ except requests .exceptions .ChunkedEncodingError :
44+ pass # Server may close stream after sending an error event
45+ return "" .join (chunks )
46+
47+
3848@step ('I use "{endpoint}" to ask question with authorization header' )
3949def ask_question_authorized (context : Context , endpoint : str ) -> None :
4050 """Call the service REST API endpoint with question."""
@@ -46,10 +56,40 @@ def ask_question_authorized(context: Context, endpoint: str) -> None:
4656 json_str = replace_placeholders (context , context .text or "{}" )
4757
4858 data = json .loads (json_str )
49- print (f"Request data: { data } " )
50- context .response = requests .post (
51- url , json = data , headers = context .auth_headers , timeout = DEFAULT_LLM_TIMEOUT
52- )
59+ if endpoint == "streaming_query" :
60+ resp = requests .post (
61+ url ,
62+ json = data ,
63+ headers = context .auth_headers ,
64+ timeout = DEFAULT_LLM_TIMEOUT ,
65+ stream = True ,
66+ )
67+ # Consume stream so server close after error event does not raise
68+ body = _read_streamed_response (resp )
69+ resp ._content = body .encode (resp .encoding or "utf-8" )
70+ context .response = resp
71+ else :
72+ context .response = requests .post (
73+ url , json = data , headers = context .auth_headers , timeout = DEFAULT_LLM_TIMEOUT
74+ )
75+
76+
77+ # Query length chosen to exceed typical model context windows (e.g. 128k tokens)
78+ _TOO_LONG_QUERY_LENGTH = 80_000
79+
80+
81+ @step ('I use "{endpoint}" to ask question with too-long query and authorization header' )
82+ def ask_question_too_long_authorized (context : Context , endpoint : str ) -> None :
83+ """Call the query endpoint with a query string that exceeds model context (expect 413)."""
84+ long_query = "what is openshift?" * _TOO_LONG_QUERY_LENGTH
85+ payload = {
86+ "query" : long_query ,
87+ "model" : context .default_model ,
88+ "provider" : context .default_provider ,
89+ }
90+ context .text = json .dumps (payload )
91+ print (f"Request: query length={ len (long_query )} , model={ context .default_model } " )
92+ ask_question_authorized (context , endpoint )
5393
5494
5595@step ("I store conversation details" )
@@ -72,7 +112,6 @@ def ask_question_in_same_conversation(context: Context, endpoint: str) -> None:
72112 headers = context .auth_headers if hasattr (context , "auth_headers" ) else {}
73113 data ["conversation_id" ] = context .response_data ["conversation_id" ]
74114
75- print (f"Request data: { data } " )
76115 context .response = requests .post (
77116 url , json = data , headers = headers , timeout = DEFAULT_LLM_TIMEOUT
78117 )
@@ -142,6 +181,29 @@ def check_streamed_fragments_in_response(context: Context) -> None:
142181 ), f"Fragment '{ expected } ' not found in LLM response: '{ response } '"
143182
144183
184+ @then ("The streamed response contains error message {message}" )
185+ def check_streamed_response_error_message (context : Context , message : str ) -> None :
186+ """Check that the streamed SSE response contains an error event with the given message.
187+
188+ Parses the response body as SSE, asserts that an event with event type 'error' is
189+ present, and that its 'response' or 'cause' field contains the given message.
190+ Use for streaming endpoints when the error is delivered in the stream (e.g. 200 + error event).
191+ """
192+ assert context .response is not None , "Request needs to be performed first"
193+ print (context .response .text )
194+ parsed = _parse_streaming_response (context .response .text )
195+ stream_error = parsed .get ("stream_error" )
196+ assert (
197+ stream_error is not None
198+ ), "No error event in stream. Expected an SSE event with event type 'error'."
199+ response_text = str (stream_error .get ("response" , "" ))
200+ cause_text = str (stream_error .get ("cause" , "" ))
201+ assert message in response_text or message in cause_text , (
202+ f"Expected error message '{ message } ' not found in stream error event: "
203+ f"response={ response_text !r} , cause={ cause_text !r} "
204+ )
205+
206+
145207@then ("The streamed response is equal to the full response" )
146208def compare_streamed_responses (context : Context ) -> None :
147209 """Check that streamed response is equal to complete response.
@@ -171,6 +233,9 @@ def _parse_streaming_response(response_text: str) -> dict:
171233 full_response_split = []
172234 finished = False
173235 first_token = True
236+ stream_error = (
237+ None # {"status_code": int, "response": str, "cause": str} if event "error"
238+ )
174239
175240 for line in lines :
176241 if line .startswith ("data: " ):
@@ -190,6 +255,8 @@ def _parse_streaming_response(response_text: str) -> dict:
190255 full_response = data ["data" ]["token" ]
191256 elif event == "end" :
192257 finished = True
258+ elif event == "error" :
259+ stream_error = data .get ("data" ) or {}
193260 except json .JSONDecodeError :
194261 continue # Skip malformed lines
195262
@@ -198,4 +265,5 @@ def _parse_streaming_response(response_text: str) -> dict:
198265 "response" : "" .join (full_response_split ),
199266 "response_complete" : full_response ,
200267 "finished" : finished ,
268+ "stream_error" : stream_error ,
201269 }
0 commit comments