test: port core's openai_conversation suite as regression net
Ported from home-assistant/core tag 2026.9.2 (tests/components/openai_conversation): conftest.py fixtures merged into tests/conftest.py, test_config_flow.py, test_conversation.py and test_ai_task.py copied and rewritten for the fork's custom_components.openai_compatible / "openai_compatible" domain. Also copied as local test infrastructure (not core-only, self-contained): - tests/__init__.py: the openai_conversation test package's own event-builder helpers (create_message_item, create_function_tool_call_item, etc.) that test_conversation.py and test_ai_task.py import via `from . import ...`. - tests/snapshots/test_conversation.ambr: just the test_model_args snapshot entry, the one surviving syrupy-based assertion (see deletions below). Deleted from test_conversation.py — depend on tests.components.conversation's MockChatLog/mock_chat_log fixture, which lives in home-assistant/core's own tests tree and is not distributable via pytest-homeassistant-custom-component (confirmed: not present in the installed package's file list): - test_function_call - test_function_call_without_reasoning - test_reasoning_summary_off_omits_summary_key - test_web_search - test_web_search_remove_citations_gpt5 - test_code_interpreter Their now-unused imports (create_code_interpreter_item, create_web_search_item, CONF_CODE_INTERPRETER, CONF_REASONING_SUMMARY, the CONF_WEB_SEARCH* constants, freezegun.freeze_time, ToolInput, datetime) were removed with them. `from tests.common import MockConfigEntry` (core's own tests package, also not shipped by pytest-homeassistant-custom-component) rewritten to `from pytest_homeassistant_custom_component.common import MockConfigEntry`, matching the convention already used in this fork's tests/test_init.py. Not run locally: this workstation is Python 3.13, HA 2026.9 (and pytest-homeassistant-custom-component) require 3.14, so no correct local interpreter exists. Validated with `python3 -m py_compile` only; CI (actions/setup-python@3.14) is the real test run. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01739Seac5eMAfNwFeiwV5r4
This commit is contained in:
1 parent
852ddc0aeb
commit
fa9648213d
6 files changed
+3210
No files matched your search
@@ -0,0 +1,449 @@
|
||||
"""Test helpers for openai_compatible (ported from core's openai_conversation test package)."""
|
||||
|
||||
from openai.types.responses import (
|
||||
ResponseCodeInterpreterCallCodeDeltaEvent,
|
||||
ResponseCodeInterpreterCallCodeDoneEvent,
|
||||
ResponseCodeInterpreterCallCompletedEvent,
|
||||
ResponseCodeInterpreterCallInProgressEvent,
|
||||
ResponseCodeInterpreterCallInterpretingEvent,
|
||||
ResponseCodeInterpreterToolCall,
|
||||
ResponseContentPartAddedEvent,
|
||||
ResponseContentPartDoneEvent,
|
||||
ResponseFunctionCallArgumentsDeltaEvent,
|
||||
ResponseFunctionCallArgumentsDoneEvent,
|
||||
ResponseFunctionToolCall,
|
||||
ResponseFunctionWebSearch,
|
||||
ResponseImageGenCallCompletedEvent,
|
||||
ResponseImageGenCallPartialImageEvent,
|
||||
ResponseOutputItemAddedEvent,
|
||||
ResponseOutputItemDoneEvent,
|
||||
ResponseOutputMessage,
|
||||
ResponseOutputText,
|
||||
ResponseReasoningItem,
|
||||
ResponseReasoningSummaryPartAddedEvent,
|
||||
ResponseReasoningSummaryPartDoneEvent,
|
||||
ResponseReasoningSummaryTextDeltaEvent,
|
||||
ResponseReasoningSummaryTextDoneEvent,
|
||||
ResponseStreamEvent,
|
||||
ResponseTextDeltaEvent,
|
||||
ResponseTextDoneEvent,
|
||||
ResponseWebSearchCallCompletedEvent,
|
||||
ResponseWebSearchCallInProgressEvent,
|
||||
ResponseWebSearchCallSearchingEvent,
|
||||
)
|
||||
from openai.types.responses.response_code_interpreter_tool_call import OutputLogs
|
||||
from openai.types.responses.response_function_web_search import ActionSearch
|
||||
from openai.types.responses.response_output_item import ImageGenerationCall
|
||||
from openai.types.responses.response_reasoning_item import Summary
|
||||
|
||||
|
||||
def create_message_item(
|
||||
id: str, text: str | list[str], output_index: int
|
||||
) -> list[ResponseStreamEvent]:
|
||||
"""Create a message item."""
|
||||
if isinstance(text, str):
|
||||
text = [text]
|
||||
|
||||
content = ResponseOutputText(annotations=[], text="", type="output_text")
|
||||
events = [
|
||||
ResponseOutputItemAddedEvent(
|
||||
item=ResponseOutputMessage(
|
||||
id=id,
|
||||
content=[],
|
||||
type="message",
|
||||
role="assistant",
|
||||
status="in_progress",
|
||||
),
|
||||
output_index=output_index,
|
||||
sequence_number=0,
|
||||
type="response.output_item.added",
|
||||
),
|
||||
ResponseContentPartAddedEvent(
|
||||
content_index=0,
|
||||
item_id=id,
|
||||
output_index=output_index,
|
||||
part=content,
|
||||
sequence_number=0,
|
||||
type="response.content_part.added",
|
||||
),
|
||||
]
|
||||
|
||||
content.text = "".join(text)
|
||||
events.extend(
|
||||
ResponseTextDeltaEvent(
|
||||
content_index=0,
|
||||
delta=delta,
|
||||
item_id=id,
|
||||
logprobs=[],
|
||||
output_index=output_index,
|
||||
sequence_number=0,
|
||||
type="response.output_text.delta",
|
||||
)
|
||||
for delta in text
|
||||
)
|
||||
|
||||
events.extend(
|
||||
[
|
||||
ResponseTextDoneEvent(
|
||||
content_index=0,
|
||||
item_id=id,
|
||||
logprobs=[],
|
||||
output_index=output_index,
|
||||
text="".join(text),
|
||||
sequence_number=0,
|
||||
type="response.output_text.done",
|
||||
),
|
||||
ResponseContentPartDoneEvent(
|
||||
content_index=0,
|
||||
item_id=id,
|
||||
output_index=output_index,
|
||||
part=content,
|
||||
sequence_number=0,
|
||||
type="response.content_part.done",
|
||||
),
|
||||
ResponseOutputItemDoneEvent(
|
||||
item=ResponseOutputMessage(
|
||||
id=id,
|
||||
content=[content],
|
||||
role="assistant",
|
||||
status="completed",
|
||||
type="message",
|
||||
),
|
||||
output_index=output_index,
|
||||
sequence_number=0,
|
||||
type="response.output_item.done",
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
return events
|
||||
|
||||
|
||||
def create_function_tool_call_item(
|
||||
id: str, arguments: str | list[str], call_id: str, name: str, output_index: int
|
||||
) -> list[ResponseStreamEvent]:
|
||||
"""Create a function tool call item."""
|
||||
if isinstance(arguments, str):
|
||||
arguments = [arguments]
|
||||
|
||||
events = [
|
||||
ResponseOutputItemAddedEvent(
|
||||
item=ResponseFunctionToolCall(
|
||||
id=id,
|
||||
arguments="",
|
||||
call_id=call_id,
|
||||
name=name,
|
||||
type="function_call",
|
||||
status="in_progress",
|
||||
),
|
||||
output_index=output_index,
|
||||
sequence_number=0,
|
||||
type="response.output_item.added",
|
||||
)
|
||||
]
|
||||
|
||||
events.extend(
|
||||
ResponseFunctionCallArgumentsDeltaEvent(
|
||||
delta=delta,
|
||||
item_id=id,
|
||||
output_index=output_index,
|
||||
sequence_number=0,
|
||||
type="response.function_call_arguments.delta",
|
||||
)
|
||||
for delta in arguments
|
||||
)
|
||||
|
||||
events.append(
|
||||
ResponseFunctionCallArgumentsDoneEvent(
|
||||
arguments="".join(arguments),
|
||||
item_id=id,
|
||||
name=name,
|
||||
output_index=output_index,
|
||||
sequence_number=0,
|
||||
type="response.function_call_arguments.done",
|
||||
)
|
||||
)
|
||||
|
||||
events.append(
|
||||
ResponseOutputItemDoneEvent(
|
||||
item=ResponseFunctionToolCall(
|
||||
id=id,
|
||||
arguments="".join(arguments),
|
||||
call_id=call_id,
|
||||
name=name,
|
||||
type="function_call",
|
||||
status="completed",
|
||||
),
|
||||
output_index=output_index,
|
||||
sequence_number=0,
|
||||
type="response.output_item.done",
|
||||
)
|
||||
)
|
||||
|
||||
return events
|
||||
|
||||
|
||||
def create_reasoning_item(
|
||||
id: str,
|
||||
output_index: int,
|
||||
reasoning_summary: list[list[str]] | list[str] | str | None = None,
|
||||
) -> list[ResponseStreamEvent]:
|
||||
"""Create a reasoning item."""
|
||||
|
||||
if reasoning_summary is None:
|
||||
reasoning_summary = [[]]
|
||||
elif isinstance(reasoning_summary, str):
|
||||
reasoning_summary = [reasoning_summary]
|
||||
if isinstance(reasoning_summary, list) and all(
|
||||
isinstance(item, str) for item in reasoning_summary
|
||||
):
|
||||
reasoning_summary = [reasoning_summary]
|
||||
|
||||
events = [
|
||||
ResponseOutputItemAddedEvent(
|
||||
item=ResponseReasoningItem(
|
||||
id=id,
|
||||
summary=[],
|
||||
type="reasoning",
|
||||
status=None,
|
||||
encrypted_content="AAA",
|
||||
),
|
||||
output_index=output_index,
|
||||
sequence_number=0,
|
||||
type="response.output_item.added",
|
||||
)
|
||||
]
|
||||
|
||||
for summary_index, summary in enumerate(reasoning_summary):
|
||||
events.append(
|
||||
ResponseReasoningSummaryPartAddedEvent(
|
||||
item_id=id,
|
||||
output_index=output_index,
|
||||
part={"text": "", "type": "summary_text"},
|
||||
sequence_number=0,
|
||||
summary_index=summary_index,
|
||||
type="response.reasoning_summary_part.added",
|
||||
)
|
||||
)
|
||||
events.extend(
|
||||
ResponseReasoningSummaryTextDeltaEvent(
|
||||
delta=delta,
|
||||
item_id=id,
|
||||
output_index=output_index,
|
||||
sequence_number=0,
|
||||
summary_index=summary_index,
|
||||
type="response.reasoning_summary_text.delta",
|
||||
)
|
||||
for delta in summary
|
||||
)
|
||||
events.extend(
|
||||
[
|
||||
ResponseReasoningSummaryTextDoneEvent(
|
||||
item_id=id,
|
||||
output_index=output_index,
|
||||
sequence_number=0,
|
||||
summary_index=summary_index,
|
||||
text="".join(summary),
|
||||
type="response.reasoning_summary_text.done",
|
||||
),
|
||||
ResponseReasoningSummaryPartDoneEvent(
|
||||
item_id=id,
|
||||
output_index=output_index,
|
||||
part={"text": "".join(summary), "type": "summary_text"},
|
||||
sequence_number=0,
|
||||
summary_index=summary_index,
|
||||
type="response.reasoning_summary_part.done",
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
events.append(
|
||||
ResponseOutputItemDoneEvent(
|
||||
item=ResponseReasoningItem(
|
||||
id=id,
|
||||
summary=[
|
||||
Summary(text="".join(summary), type="summary_text")
|
||||
for summary in reasoning_summary
|
||||
],
|
||||
type="reasoning",
|
||||
status=None,
|
||||
encrypted_content="AAABBB",
|
||||
),
|
||||
output_index=output_index,
|
||||
sequence_number=0,
|
||||
type="response.output_item.done",
|
||||
),
|
||||
)
|
||||
|
||||
return events
|
||||
|
||||
|
||||
def create_web_search_item(id: str, output_index: int) -> list[ResponseStreamEvent]:
|
||||
"""Create a web search call item."""
|
||||
return [
|
||||
ResponseOutputItemAddedEvent(
|
||||
item=ResponseFunctionWebSearch(
|
||||
id=id,
|
||||
status="in_progress",
|
||||
action=ActionSearch(query="query", type="search"),
|
||||
type="web_search_call",
|
||||
),
|
||||
output_index=output_index,
|
||||
sequence_number=0,
|
||||
type="response.output_item.added",
|
||||
),
|
||||
ResponseWebSearchCallInProgressEvent(
|
||||
item_id=id,
|
||||
output_index=output_index,
|
||||
sequence_number=0,
|
||||
type="response.web_search_call.in_progress",
|
||||
),
|
||||
ResponseWebSearchCallSearchingEvent(
|
||||
item_id=id,
|
||||
output_index=output_index,
|
||||
sequence_number=0,
|
||||
type="response.web_search_call.searching",
|
||||
),
|
||||
ResponseWebSearchCallCompletedEvent(
|
||||
item_id=id,
|
||||
output_index=output_index,
|
||||
sequence_number=0,
|
||||
type="response.web_search_call.completed",
|
||||
),
|
||||
ResponseOutputItemDoneEvent(
|
||||
item=ResponseFunctionWebSearch(
|
||||
id=id,
|
||||
status="completed",
|
||||
action=ActionSearch(query="query", type="search"),
|
||||
type="web_search_call",
|
||||
),
|
||||
output_index=output_index,
|
||||
sequence_number=0,
|
||||
type="response.output_item.done",
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
def create_code_interpreter_item(
|
||||
id: str, code: str | list[str], output_index: int, logs: str | None = None
|
||||
) -> list[ResponseStreamEvent]:
|
||||
"""Create a message item."""
|
||||
if isinstance(code, str):
|
||||
code = [code]
|
||||
|
||||
container_id = "cntr_A"
|
||||
events = [
|
||||
ResponseOutputItemAddedEvent(
|
||||
item=ResponseCodeInterpreterToolCall(
|
||||
id=id,
|
||||
code="",
|
||||
container_id=container_id,
|
||||
outputs=None,
|
||||
type="code_interpreter_call",
|
||||
status="in_progress",
|
||||
),
|
||||
output_index=output_index,
|
||||
sequence_number=0,
|
||||
type="response.output_item.added",
|
||||
),
|
||||
ResponseCodeInterpreterCallInProgressEvent(
|
||||
item_id=id,
|
||||
output_index=output_index,
|
||||
sequence_number=0,
|
||||
type="response.code_interpreter_call.in_progress",
|
||||
),
|
||||
]
|
||||
|
||||
events.extend(
|
||||
ResponseCodeInterpreterCallCodeDeltaEvent(
|
||||
delta=delta,
|
||||
item_id=id,
|
||||
output_index=output_index,
|
||||
sequence_number=0,
|
||||
type="response.code_interpreter_call_code.delta",
|
||||
)
|
||||
for delta in code
|
||||
)
|
||||
|
||||
code = "".join(code)
|
||||
|
||||
events.extend(
|
||||
[
|
||||
ResponseCodeInterpreterCallCodeDoneEvent(
|
||||
item_id=id,
|
||||
output_index=output_index,
|
||||
code=code,
|
||||
sequence_number=0,
|
||||
type="response.code_interpreter_call_code.done",
|
||||
),
|
||||
ResponseCodeInterpreterCallInterpretingEvent(
|
||||
item_id=id,
|
||||
output_index=output_index,
|
||||
sequence_number=0,
|
||||
type="response.code_interpreter_call.interpreting",
|
||||
),
|
||||
ResponseCodeInterpreterCallCompletedEvent(
|
||||
item_id=id,
|
||||
output_index=output_index,
|
||||
sequence_number=0,
|
||||
type="response.code_interpreter_call.completed",
|
||||
),
|
||||
ResponseOutputItemDoneEvent(
|
||||
item=ResponseCodeInterpreterToolCall(
|
||||
id=id,
|
||||
code=code,
|
||||
container_id=container_id,
|
||||
outputs=[OutputLogs(type="logs", logs=logs)] if logs else None,
|
||||
status="completed",
|
||||
type="code_interpreter_call",
|
||||
),
|
||||
output_index=output_index,
|
||||
sequence_number=0,
|
||||
type="response.output_item.done",
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
return events
|
||||
|
||||
|
||||
def create_image_gen_call_item(
|
||||
id: str, output_index: int, logs: str | None = None
|
||||
) -> list[ResponseStreamEvent]:
|
||||
"""Create a message item."""
|
||||
return [
|
||||
ResponseImageGenCallPartialImageEvent(
|
||||
item_id=id,
|
||||
output_index=output_index,
|
||||
partial_image_b64="QQ==",
|
||||
partial_image_index=0,
|
||||
sequence_number=0,
|
||||
type="response.image_generation_call.partial_image",
|
||||
size="1536x1024",
|
||||
quality="medium",
|
||||
background="transparent",
|
||||
output_format="png",
|
||||
),
|
||||
ResponseImageGenCallCompletedEvent(
|
||||
item_id=id,
|
||||
output_index=output_index,
|
||||
sequence_number=0,
|
||||
type="response.image_generation_call.completed",
|
||||
),
|
||||
ResponseOutputItemDoneEvent(
|
||||
item=ImageGenerationCall(
|
||||
id=id,
|
||||
result="QQ==",
|
||||
status="completed",
|
||||
type="image_generation_call",
|
||||
background="transparent",
|
||||
output_format="png",
|
||||
quality="medium",
|
||||
revised_prompt="Mock revised prompt.",
|
||||
size="1536x1024",
|
||||
),
|
||||
output_index=output_index,
|
||||
sequence_number=0,
|
||||
type="response.output_item.done",
|
||||
),
|
||||
]
|
||||
Reference in new issue
Block a user