1515"""Resilience tests for Responses input/output conversion helpers."""
1616
1717from dataclasses import dataclass
18- from typing import Any , Optional
18+ from typing import Any , Optional , cast
1919
2020import pytest
21+ from openai .types .responses import ResponseOutputItem
22+ from openai .types .responses import ResponseInputItemParam
2123
2224from opentelemetry .instrumentation .openai_v2 .responses import (
2325 output_to_event ,
2628from opentelemetry .instrumentation .openai_v2 .responses_patch import (
2729 _log_responses_inputs ,
2830)
31+ from opentelemetry ._logs import Logger
2932
3033
3134@dataclass
@@ -49,6 +52,13 @@ def emit(self, record: Any) -> None:
4952 self .emitted .append (record )
5053
5154
55+ def _as_output_item (value : Any ) -> ResponseOutputItem :
56+ # These tests intentionally pass minimal "shape-only" objects to exercise
57+ # resilience for new/unknown output types. Cast keeps type-checkers happy
58+ # without weakening the production signature.
59+ return cast (ResponseOutputItem , value )
60+
61+
5262@pytest .mark .parametrize (
5363 "input_data" ,
5464 [
@@ -145,10 +155,16 @@ def emit(self, record: Any) -> None:
145155def test_input_type_handler_does_not_crash (input_data ):
146156 """Each known input type should be processed without exceptions."""
147157 assert (
148- responses_input_to_event (input_data , capture_content = True ) is not None
158+ responses_input_to_event (
159+ cast (ResponseInputItemParam , input_data ), capture_content = True
160+ )
161+ is not None
149162 )
150163 assert (
151- responses_input_to_event (input_data , capture_content = False ) is not None
164+ responses_input_to_event (
165+ cast (ResponseInputItemParam , input_data ), capture_content = False
166+ )
167+ is not None
152168 )
153169
154170
@@ -166,19 +182,19 @@ def test_string_input_does_not_crash():
166182def test_responses_create_input_none_is_noop_for_logging ():
167183 logger = _CapturingLogger ()
168184
169- _log_responses_inputs (logger , {"input" : None }, capture_content = True )
170- _log_responses_inputs (logger , {"input" : None }, capture_content = False )
185+ _log_responses_inputs (cast ( Logger , logger ) , {"input" : None }, capture_content = True )
186+ _log_responses_inputs (cast ( Logger , logger ) , {"input" : None }, capture_content = False )
171187
172- assert logger .emitted == []
188+ assert not logger .emitted
173189
174190
175191def test_responses_create_input_omitted_is_noop_for_logging ():
176192 logger = _CapturingLogger ()
177193
178- _log_responses_inputs (logger , {}, capture_content = True )
179- _log_responses_inputs (logger , {}, capture_content = False )
194+ _log_responses_inputs (cast ( Logger , logger ) , {}, capture_content = True )
195+ _log_responses_inputs (cast ( Logger , logger ) , {}, capture_content = False )
180196
181- assert logger .emitted == []
197+ assert not logger .emitted
182198
183199
184200@pytest .mark .parametrize (
@@ -204,7 +220,9 @@ def test_responses_create_input_omitted_is_noop_for_logging():
204220)
205221def test_unexpected_input_does_not_crash (unexpected_input ):
206222 # Should not raise
207- responses_input_to_event (unexpected_input , capture_content = True )
223+ responses_input_to_event (
224+ cast (ResponseInputItemParam , unexpected_input ), capture_content = True
225+ )
208226
209227
210228def test_deeply_nested_content_does_not_crash ():
@@ -222,7 +240,9 @@ def test_deeply_nested_content_does_not_crash():
222240 ],
223241 }
224242 assert (
225- responses_input_to_event (nested_input , capture_content = True )
243+ responses_input_to_event (
244+ cast (ResponseInputItemParam , nested_input ), capture_content = True
245+ )
226246 is not None
227247 )
228248
@@ -240,29 +260,37 @@ def test_future_union_types_are_best_effort():
240260 "name" : "some_tool" ,
241261 "arguments" : {"x" : 1 },
242262 }
243- event = responses_input_to_event (future_call , capture_content = True )
263+ event = responses_input_to_event (
264+ cast (ResponseInputItemParam , future_call ), capture_content = True
265+ )
244266 assert event is not None
245267 assert event .event_name == "gen_ai.assistant.input"
246- assert event .body and event .body .get ("type" ) == "future_provider_tool_call"
268+ body : Any = event .body
269+ assert body and body .get ("type" ) == "future_provider_tool_call"
247270
248271 # New tool call output (tool -> model): routed by `*_call_output`
249272 future_call_output = {
250273 "type" : "future_provider_tool_call_output" ,
251274 "call_id" : "call_123" ,
252275 "output" : "ok" ,
253276 }
254- event = responses_input_to_event (future_call_output , capture_content = True )
277+ event = responses_input_to_event (
278+ cast (ResponseInputItemParam , future_call_output ), capture_content = True
279+ )
255280 assert event is not None
256281 assert event .event_name == "gen_ai.tool.input"
257- assert event .body and event .body .get ("id" ) == "call_123"
282+ body = event .body
283+ assert body and body .get ("id" ) == "call_123"
258284
259285 # Another output naming style: routed by `*_output`
260286 future_output_alt = {
261287 "type" : "future_provider_tool_output" ,
262288 "id" : "id_999" ,
263289 "output" : [{"type" : "text" , "text" : "ok" }],
264290 }
265- event = responses_input_to_event (future_output_alt , capture_content = True )
291+ event = responses_input_to_event (
292+ cast (ResponseInputItemParam , future_output_alt ), capture_content = True
293+ )
266294 assert event is not None
267295 assert event .event_name == "gen_ai.tool.input"
268296
@@ -279,8 +307,14 @@ class MockOutput:
279307 content = [ContentPart ()]
280308 index = 0
281309
282- assert output_to_event (MockOutput (), capture_content = True ) is not None # type: ignore[arg-type]
283- assert output_to_event (MockOutput (), capture_content = False ) is not None # type: ignore[arg-type]
310+ assert (
311+ output_to_event (_as_output_item (MockOutput ()), capture_content = True )
312+ is not None
313+ )
314+ assert (
315+ output_to_event (_as_output_item (MockOutput ()), capture_content = False )
316+ is not None
317+ )
284318
285319
286320def test_function_call_output_does_not_crash ():
@@ -292,7 +326,10 @@ class MockOutput:
292326 status = "completed"
293327 index = 0
294328
295- assert output_to_event (MockOutput (), capture_content = True ) is not None # type: ignore[arg-type]
329+ assert (
330+ output_to_event (_as_output_item (MockOutput ()), capture_content = True )
331+ is not None
332+ )
296333
297334
298335def test_reasoning_output_does_not_crash ():
@@ -306,7 +343,10 @@ class MockOutput:
306343 status = "completed"
307344 summary = [Part ()]
308345
309- assert output_to_event (MockOutput (), capture_content = True ) is not None # type: ignore[arg-type]
346+ assert (
347+ output_to_event (_as_output_item (MockOutput ()), capture_content = True )
348+ is not None
349+ )
310350
311351
312352@pytest .mark .parametrize (
@@ -328,7 +368,7 @@ def test_tool_call_outputs_do_not_crash(output_type):
328368 name = "tool_name" ,
329369 status = "completed" ,
330370 )
331- assert output_to_event (mock , capture_content = True ) is not None # type: ignore[arg-type]
371+ assert output_to_event (_as_output_item ( mock ) , capture_content = True ) is not None
332372
333373
334374def test_unknown_output_type_does_not_crash ():
@@ -337,14 +377,20 @@ class MockOutput:
337377 id = "id_123"
338378 status = "completed"
339379
340- assert output_to_event (MockOutput (), capture_content = True ) is not None # type: ignore[arg-type]
380+ assert (
381+ output_to_event (_as_output_item (MockOutput ()), capture_content = True )
382+ is not None
383+ )
341384
342385
343386def test_minimal_output_does_not_crash ():
344387 class MockOutput :
345388 type = "message"
346389
347- assert output_to_event (MockOutput (), capture_content = True ) is not None # type: ignore[arg-type]
390+ assert (
391+ output_to_event (_as_output_item (MockOutput ()), capture_content = True )
392+ is not None
393+ )
348394
349395
350396def test_output_with_none_values_does_not_crash ():
@@ -355,4 +401,7 @@ class MockOutput:
355401 content = None
356402 index = None
357403
358- assert output_to_event (MockOutput (), capture_content = True ) is not None # type: ignore[arg-type]
404+ assert (
405+ output_to_event (_as_output_item (MockOutput ()), capture_content = True )
406+ is not None
407+ )
0 commit comments