mirror of
https://github.com/home-assistant/core.git
synced 2026-09-26 17:31:15 -04:00
Update Anthropic tests (#182940)
This commit is contained in:
@@ -201,7 +201,7 @@
|
||||
}),
|
||||
])
|
||||
# ---
|
||||
# name: test_disabled_thinking[subentry_data0]
|
||||
# name: test_disabled_thinking[zero_budget]
|
||||
list([
|
||||
dict({
|
||||
'content': '''
|
||||
@@ -230,7 +230,7 @@
|
||||
}),
|
||||
])
|
||||
# ---
|
||||
# name: test_disabled_thinking[subentry_data0].1
|
||||
# name: test_disabled_thinking[zero_budget].1
|
||||
dict({
|
||||
'container': None,
|
||||
'max_tokens': 3000,
|
||||
@@ -265,7 +265,7 @@
|
||||
}),
|
||||
})
|
||||
# ---
|
||||
# name: test_disabled_thinking[subentry_data1]
|
||||
# name: test_disabled_thinking[no_effort]
|
||||
list([
|
||||
dict({
|
||||
'content': '''
|
||||
@@ -294,7 +294,7 @@
|
||||
}),
|
||||
])
|
||||
# ---
|
||||
# name: test_disabled_thinking[subentry_data1].1
|
||||
# name: test_disabled_thinking[no_effort].1
|
||||
dict({
|
||||
'container': None,
|
||||
'max_tokens': 3000,
|
||||
@@ -513,7 +513,7 @@
|
||||
}),
|
||||
])
|
||||
# ---
|
||||
# name: test_history_conversion[content0]
|
||||
# name: test_history_conversion[system_only]
|
||||
list([
|
||||
dict({
|
||||
'content': 'Are you sure?',
|
||||
@@ -525,7 +525,7 @@
|
||||
}),
|
||||
])
|
||||
# ---
|
||||
# name: test_history_conversion[content1]
|
||||
# name: test_history_conversion[single_exchange]
|
||||
list([
|
||||
dict({
|
||||
'content': 'What shape is a donut?',
|
||||
@@ -545,7 +545,7 @@
|
||||
}),
|
||||
])
|
||||
# ---
|
||||
# name: test_history_conversion[content2]
|
||||
# name: test_history_conversion[two_consecutive_messages]
|
||||
list([
|
||||
dict({
|
||||
'content': list([
|
||||
@@ -583,7 +583,7 @@
|
||||
}),
|
||||
])
|
||||
# ---
|
||||
# name: test_history_conversion[content3]
|
||||
# name: test_history_conversion[three_consecutive_messages]
|
||||
list([
|
||||
dict({
|
||||
'content': list([
|
||||
@@ -629,7 +629,7 @@
|
||||
}),
|
||||
])
|
||||
# ---
|
||||
# name: test_history_conversion[content4]
|
||||
# name: test_history_conversion[tool_results]
|
||||
list([
|
||||
dict({
|
||||
'content': 'Turn off the lights and make me coffee',
|
||||
@@ -694,7 +694,7 @@
|
||||
}),
|
||||
])
|
||||
# ---
|
||||
# name: test_history_conversion[content5]
|
||||
# name: test_history_conversion[web_search_citations]
|
||||
list([
|
||||
dict({
|
||||
'content': "What's on the news today?",
|
||||
@@ -806,7 +806,7 @@
|
||||
}),
|
||||
])
|
||||
# ---
|
||||
# name: test_history_conversion[content6]
|
||||
# name: test_history_conversion[web_fetch_citations]
|
||||
list([
|
||||
dict({
|
||||
'content': "What's new in Home Assistant?",
|
||||
@@ -899,7 +899,7 @@
|
||||
}),
|
||||
])
|
||||
# ---
|
||||
# name: test_history_conversion[content7]
|
||||
# name: test_history_conversion[time_in_tool_result]
|
||||
list([
|
||||
dict({
|
||||
'content': 'What time is it?',
|
||||
@@ -946,7 +946,7 @@
|
||||
}),
|
||||
])
|
||||
# ---
|
||||
# name: test_history_conversion[content8]
|
||||
# name: test_history_conversion[tool_search_results]
|
||||
list([
|
||||
dict({
|
||||
'content': 'Set humidity to 50%',
|
||||
@@ -1088,7 +1088,7 @@
|
||||
}),
|
||||
])
|
||||
# ---
|
||||
# name: test_text_editor_code_execution[args_parts0-content0]
|
||||
# name: test_text_editor_code_execution[create_file]
|
||||
list([
|
||||
dict({
|
||||
'attachments': None,
|
||||
@@ -1147,7 +1147,7 @@
|
||||
}),
|
||||
])
|
||||
# ---
|
||||
# name: test_text_editor_code_execution[args_parts0-content0].1
|
||||
# name: test_text_editor_code_execution[create_file].1
|
||||
list([
|
||||
dict({
|
||||
'content': 'Do the needful',
|
||||
@@ -1186,7 +1186,7 @@
|
||||
}),
|
||||
])
|
||||
# ---
|
||||
# name: test_text_editor_code_execution[args_parts1-content1]
|
||||
# name: test_text_editor_code_execution[replace_text]
|
||||
list([
|
||||
dict({
|
||||
'attachments': None,
|
||||
@@ -1255,7 +1255,7 @@
|
||||
}),
|
||||
])
|
||||
# ---
|
||||
# name: test_text_editor_code_execution[args_parts1-content1].1
|
||||
# name: test_text_editor_code_execution[replace_text].1
|
||||
list([
|
||||
dict({
|
||||
'content': 'Do the needful',
|
||||
@@ -1304,7 +1304,7 @@
|
||||
}),
|
||||
])
|
||||
# ---
|
||||
# name: test_text_editor_code_execution[args_parts2-content2]
|
||||
# name: test_text_editor_code_execution[view_file]
|
||||
list([
|
||||
dict({
|
||||
'attachments': None,
|
||||
@@ -1366,7 +1366,7 @@
|
||||
}),
|
||||
])
|
||||
# ---
|
||||
# name: test_text_editor_code_execution[args_parts2-content2].1
|
||||
# name: test_text_editor_code_execution[view_file].1
|
||||
list([
|
||||
dict({
|
||||
'content': 'Do the needful',
|
||||
@@ -1408,7 +1408,7 @@
|
||||
}),
|
||||
])
|
||||
# ---
|
||||
# name: test_text_editor_code_execution[args_parts3-content3]
|
||||
# name: test_text_editor_code_execution[tool_error]
|
||||
list([
|
||||
dict({
|
||||
'attachments': None,
|
||||
@@ -1467,7 +1467,7 @@
|
||||
}),
|
||||
])
|
||||
# ---
|
||||
# name: test_text_editor_code_execution[args_parts3-content3].1
|
||||
# name: test_text_editor_code_execution[tool_error].1
|
||||
list([
|
||||
dict({
|
||||
'content': 'Do the needful',
|
||||
|
||||
@@ -24,10 +24,10 @@ from . import create_content_block, create_thinking_block, create_tool_use_block
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_init_component")
|
||||
async def test_generate_data(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_init_component,
|
||||
mock_create_stream: AsyncMock,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
@@ -59,10 +59,8 @@ async def test_generate_data(
|
||||
assert result.data == "The test data"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_init_component")
|
||||
async def test_translation_key(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_init_component,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test entity translation key."""
|
||||
@@ -71,10 +69,9 @@ async def test_translation_key(
|
||||
assert entry.translation_key == "ai_task_data"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_init_component")
|
||||
async def test_empty_data(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_init_component,
|
||||
mock_create_stream: AsyncMock,
|
||||
) -> None:
|
||||
"""Test AI Task data generation but the data returned is empty."""
|
||||
@@ -91,10 +88,9 @@ async def test_empty_data(
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_init_component")
|
||||
async def test_stream_wrong_type(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_init_component,
|
||||
mock_create_stream: AsyncMock,
|
||||
) -> None:
|
||||
"""Test error if the response is not a stream."""
|
||||
@@ -117,10 +113,10 @@ async def test_stream_wrong_type(
|
||||
|
||||
|
||||
@freeze_time("2026-01-01 12:00:00")
|
||||
@pytest.mark.usefixtures("mock_init_component")
|
||||
async def test_generate_structured_data_legacy(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_init_component,
|
||||
mock_create_stream: AsyncMock,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
@@ -168,10 +164,10 @@ async def test_generate_structured_data_legacy(
|
||||
|
||||
|
||||
@freeze_time("2026-01-01 12:00:00")
|
||||
@pytest.mark.usefixtures("mock_init_component")
|
||||
async def test_generate_structured_data_legacy_tools(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_init_component,
|
||||
mock_create_stream: AsyncMock,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
@@ -220,10 +216,10 @@ async def test_generate_structured_data_legacy_tools(
|
||||
|
||||
|
||||
@freeze_time("2026-01-01 12:00:00")
|
||||
@pytest.mark.usefixtures("mock_init_component")
|
||||
async def test_generate_structured_data_legacy_extended_thinking(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_init_component,
|
||||
mock_create_stream: AsyncMock,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
@@ -280,10 +276,10 @@ async def test_generate_structured_data_legacy_extended_thinking(
|
||||
|
||||
|
||||
@freeze_time("2026-01-01 12:00:00")
|
||||
@pytest.mark.usefixtures("mock_init_component")
|
||||
async def test_generate_structured_data_legacy_extra_text_block(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_init_component,
|
||||
mock_create_stream: AsyncMock,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
@@ -340,10 +336,10 @@ async def test_generate_structured_data_legacy_extra_text_block(
|
||||
assert mock_create_stream.call_args.kwargs.copy() == snapshot
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_init_component")
|
||||
async def test_generate_invalid_structured_data_legacy(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_init_component,
|
||||
mock_create_stream: AsyncMock,
|
||||
) -> None:
|
||||
"""Test AI Task with invalid JSON response with legacy method."""
|
||||
@@ -389,10 +385,9 @@ async def test_generate_invalid_structured_data_legacy(
|
||||
|
||||
|
||||
@freeze_time("2026-01-01 12:00:00")
|
||||
@pytest.mark.usefixtures("mock_init_component")
|
||||
async def test_generate_structured_data(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_init_component,
|
||||
mock_create_stream: AsyncMock,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
@@ -423,12 +418,10 @@ async def test_generate_structured_data(
|
||||
assert mock_create_stream.call_args.kwargs.copy() == snapshot
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_init_component")
|
||||
async def test_generate_data_with_attachments(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_init_component,
|
||||
mock_create_stream: AsyncMock,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test AI Task data generation with attachments."""
|
||||
entity_id = "ai_task.claude_ai_task"
|
||||
@@ -505,12 +498,10 @@ async def test_generate_data_with_attachments(
|
||||
assert document_block["source"]["type"] == "base64"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_init_component")
|
||||
async def test_generate_data_invalid_attachments(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_init_component,
|
||||
mock_create_stream: AsyncMock,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test AI Task data generation with attachments of unsupported type."""
|
||||
entity_id = "ai_task.claude_ai_task"
|
||||
@@ -581,10 +572,9 @@ async def test_generate_data_invalid_attachments(
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_init_component")
|
||||
async def test_generate_data_with_attachments_whitespace_instructions(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_init_component,
|
||||
mock_create_stream: AsyncMock,
|
||||
) -> None:
|
||||
"""Test whitespace-only instructions with attachments produce no text block.
|
||||
|
||||
@@ -5,6 +5,7 @@ from unittest.mock import AsyncMock, patch
|
||||
|
||||
from anthropic import (
|
||||
APIConnectionError,
|
||||
APIError,
|
||||
APIResponseValidationError,
|
||||
APITimeoutError,
|
||||
AuthenticationError,
|
||||
@@ -53,7 +54,10 @@ from homeassistant.data_entry_flow import FlowResultType
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def test_form(hass: HomeAssistant, mock_setup_entry) -> None:
|
||||
async def test_form(
|
||||
hass: HomeAssistant,
|
||||
mock_setup_entry: AsyncMock,
|
||||
) -> None:
|
||||
"""Test we get the form."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
@@ -94,7 +98,10 @@ async def test_form(hass: HomeAssistant, mock_setup_entry) -> None:
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_duplicate_entry(hass: HomeAssistant, mock_config_entry) -> None:
|
||||
async def test_duplicate_entry(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test we abort on duplicate config entry."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
@@ -117,8 +124,10 @@ async def test_duplicate_entry(hass: HomeAssistant, mock_config_entry) -> None:
|
||||
assert result["reason"] == "already_configured"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_init_component")
|
||||
async def test_creating_conversation_subentry(
|
||||
hass: HomeAssistant, mock_config_entry, mock_init_component
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test creating a conversation subentry."""
|
||||
result = await hass.config_entries.subentries.async_init(
|
||||
@@ -144,9 +153,9 @@ async def test_creating_conversation_subentry(
|
||||
assert result2["data"] == processed_options
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_init_component")
|
||||
async def test_creating_conversation_subentry_not_loaded(
|
||||
hass: HomeAssistant,
|
||||
mock_init_component,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test creating a conversation subentry when entry is not loaded."""
|
||||
@@ -167,9 +176,11 @@ async def test_creating_conversation_subentry_not_loaded(
|
||||
@pytest.mark.parametrize(
|
||||
("side_effect", "error"),
|
||||
[
|
||||
(APIConnectionError(request=None), "cannot_connect"),
|
||||
(APITimeoutError(request=None), "timeout_connect"),
|
||||
(
|
||||
pytest.param(
|
||||
APIConnectionError(request=None), "cannot_connect", id="connection_error"
|
||||
),
|
||||
pytest.param(APITimeoutError(request=None), "timeout_connect", id="timeout"),
|
||||
pytest.param(
|
||||
BadRequestError(
|
||||
message=(
|
||||
"Your credit balance is too low to access"
|
||||
@@ -183,8 +194,9 @@ async def test_creating_conversation_subentry_not_loaded(
|
||||
body={"type": "error", "error": {"type": "invalid_request_error"}},
|
||||
),
|
||||
"unknown",
|
||||
id="insufficient_credit",
|
||||
),
|
||||
(
|
||||
pytest.param(
|
||||
AuthenticationError(
|
||||
message="invalid x-api-key",
|
||||
response=Response(
|
||||
@@ -194,8 +206,9 @@ async def test_creating_conversation_subentry_not_loaded(
|
||||
body={"type": "error", "error": {"type": "authentication_error"}},
|
||||
),
|
||||
"authentication_error",
|
||||
id="authentication_error",
|
||||
),
|
||||
(
|
||||
pytest.param(
|
||||
InternalServerError(
|
||||
message=None,
|
||||
response=Response(
|
||||
@@ -205,8 +218,9 @@ async def test_creating_conversation_subentry_not_loaded(
|
||||
body=None,
|
||||
),
|
||||
"unknown",
|
||||
id="server_error",
|
||||
),
|
||||
(
|
||||
pytest.param(
|
||||
APIResponseValidationError(
|
||||
response=Response(
|
||||
status_code=200,
|
||||
@@ -215,11 +229,16 @@ async def test_creating_conversation_subentry_not_loaded(
|
||||
body=None,
|
||||
),
|
||||
"unknown",
|
||||
id="invalid_response",
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.usefixtures("mock_setup_entry")
|
||||
async def test_api_error(hass: HomeAssistant, side_effect, error) -> None:
|
||||
async def test_api_error(
|
||||
hass: HomeAssistant,
|
||||
side_effect: APIError,
|
||||
error: str,
|
||||
) -> None:
|
||||
"""Test that we handle API errors."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
@@ -257,8 +276,10 @@ async def test_api_error(hass: HomeAssistant, side_effect, error) -> None:
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_init_component")
|
||||
async def test_subentry_options_thinking_budget_more_than_max(
|
||||
hass: HomeAssistant, mock_config_entry, mock_init_component
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test error about thinking budget being more than max tokens."""
|
||||
subentry = next(iter(mock_config_entry.subentries.values()))
|
||||
@@ -312,8 +333,10 @@ async def test_subentry_options_thinking_budget_more_than_max(
|
||||
assert subentry.data["thinking_budget"] == 8192
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_init_component")
|
||||
async def test_subentry_web_search_user_location(
|
||||
hass: HomeAssistant, mock_config_entry, mock_init_component
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test fetching user location."""
|
||||
subentry = next(iter(mock_config_entry.subentries.values()))
|
||||
@@ -403,10 +426,10 @@ async def test_subentry_web_search_user_location(
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_init_component")
|
||||
async def test_model_list(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry,
|
||||
mock_init_component,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test fetching and processing the list of models."""
|
||||
@@ -428,8 +451,10 @@ async def test_model_list(
|
||||
assert options["data_schema"].schema["chat_model"].config["options"] == snapshot
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_init_component")
|
||||
async def test_invalid_model(
|
||||
hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_init_component: None
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test exceptions during fetching model info."""
|
||||
options = await hass.config_entries.subentries.async_init(
|
||||
@@ -534,7 +559,7 @@ async def test_invalid_model(
|
||||
@pytest.mark.parametrize(
|
||||
("current_options", "new_options", "expected_options"),
|
||||
[
|
||||
( # Test converting single llm api format to list
|
||||
pytest.param( # Test converting single llm api format to list
|
||||
{
|
||||
CONF_RECOMMENDED: True,
|
||||
CONF_PROMPT: "",
|
||||
@@ -552,8 +577,9 @@ async def test_invalid_model(
|
||||
CONF_PROMPT: "",
|
||||
CONF_LLM_HASS_API: ["assist"],
|
||||
},
|
||||
id="recommended_to_recommended",
|
||||
),
|
||||
( # Model with web search options
|
||||
pytest.param( # Model with web search options
|
||||
{
|
||||
CONF_RECOMMENDED: False,
|
||||
CONF_CHAT_MODEL: "claude-sonnet-4-5",
|
||||
@@ -604,8 +630,9 @@ async def test_invalid_model(
|
||||
CONF_WEB_SEARCH_USER_LOCATION: False,
|
||||
CONF_CODE_EXECUTION: False,
|
||||
},
|
||||
id="disable_web_tools",
|
||||
),
|
||||
( # Model with thinking budget options
|
||||
pytest.param( # Model with thinking budget options
|
||||
{
|
||||
CONF_RECOMMENDED: False,
|
||||
CONF_CHAT_MODEL: "claude-sonnet-4-5",
|
||||
@@ -656,8 +683,9 @@ async def test_invalid_model(
|
||||
CONF_WEB_SEARCH_USER_LOCATION: False,
|
||||
CONF_CODE_EXECUTION: False,
|
||||
},
|
||||
id="update_thinking_budget",
|
||||
),
|
||||
( # Model with thinking effort options
|
||||
pytest.param( # Model with thinking effort options
|
||||
{
|
||||
CONF_RECOMMENDED: False,
|
||||
CONF_CHAT_MODEL: "claude-fable-5",
|
||||
@@ -709,8 +737,9 @@ async def test_invalid_model(
|
||||
CONF_WEB_SEARCH_USER_LOCATION: False,
|
||||
CONF_CODE_EXECUTION: True,
|
||||
},
|
||||
id="update_thinking_effort",
|
||||
),
|
||||
( # Test switching from recommended to custom options
|
||||
pytest.param( # Test switching from recommended to custom options
|
||||
{
|
||||
CONF_RECOMMENDED: True,
|
||||
CONF_PROMPT: "bla",
|
||||
@@ -742,8 +771,9 @@ async def test_invalid_model(
|
||||
CONF_WEB_FETCH_MAX_USES: 5,
|
||||
CONF_CODE_EXECUTION: False,
|
||||
},
|
||||
id="recommended_to_custom",
|
||||
),
|
||||
( # Test switching from custom to recommended options
|
||||
pytest.param( # Test switching from custom to recommended options
|
||||
{
|
||||
CONF_RECOMMENDED: False,
|
||||
CONF_PROMPT: "Speak like a pirate",
|
||||
@@ -771,16 +801,17 @@ async def test_invalid_model(
|
||||
CONF_LLM_HASS_API: ["assist"],
|
||||
CONF_PROMPT: "",
|
||||
},
|
||||
id="custom_to_recommended",
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.usefixtures("mock_init_component")
|
||||
async def test_subentry_options_switching(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry,
|
||||
mock_init_component,
|
||||
current_options,
|
||||
new_options,
|
||||
expected_options,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
current_options: dict[str, str | int | bool | list[str]],
|
||||
new_options: tuple[dict[str, str | int | bool | list[str]], ...],
|
||||
expected_options: dict[str, str | int | bool | list[str]],
|
||||
) -> None:
|
||||
"""Test the subentry options form."""
|
||||
subentry = next(iter(mock_config_entry.subentries.values()))
|
||||
@@ -822,10 +853,10 @@ async def test_subentry_options_switching(
|
||||
assert subentry.data == expected_options
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_init_component")
|
||||
async def test_creating_ai_task_subentry(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_init_component,
|
||||
) -> None:
|
||||
"""Test creating an AI task subentry."""
|
||||
old_subentries = set(mock_config_entry.subentries)
|
||||
@@ -880,10 +911,10 @@ async def test_ai_task_subentry_not_loaded(
|
||||
assert result.get("reason") == "entry_not_loaded"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_init_component")
|
||||
async def test_creating_ai_task_subentry_additional(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_init_component,
|
||||
) -> None:
|
||||
"""Test creating an AI task subentry with additional settings."""
|
||||
result = await hass.config_entries.subentries.async_init(
|
||||
@@ -945,7 +976,9 @@ async def test_creating_ai_task_subentry_additional(
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_setup_entry")
|
||||
async def test_reauth(hass: HomeAssistant) -> None:
|
||||
async def test_reauth(
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""Test we can reauthenticate."""
|
||||
# Pretend we already set up a config entry.
|
||||
hass.config.components.add("anthropic")
|
||||
@@ -980,18 +1013,20 @@ async def test_reauth(hass: HomeAssistant) -> None:
|
||||
@pytest.mark.parametrize(
|
||||
("current_llm_apis", "suggested_llm_apis", "expected_options"),
|
||||
[
|
||||
("assist", ["assist"], ["assist"]),
|
||||
(["assist"], ["assist"], ["assist"]),
|
||||
("non-existent", [], ["assist"]),
|
||||
(["non-existent"], [], ["assist"]),
|
||||
(["assist", "non-existent"], ["assist"], ["assist"]),
|
||||
pytest.param("assist", ["assist"], ["assist"], id="assist_string"),
|
||||
pytest.param(["assist"], ["assist"], ["assist"], id="assist_list"),
|
||||
pytest.param("non-existent", [], ["assist"], id="unknown_string"),
|
||||
pytest.param(["non-existent"], [], ["assist"], id="unknown_list"),
|
||||
pytest.param(
|
||||
["assist", "non-existent"], ["assist"], ["assist"], id="mixed_list"
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.usefixtures("mock_init_component")
|
||||
async def test_reconfigure_conversation_subentry_llm_api_schema(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_init_component,
|
||||
current_llm_apis: list[str],
|
||||
current_llm_apis: str | list[str],
|
||||
suggested_llm_apis: list[str],
|
||||
expected_options: list[str],
|
||||
) -> None:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,7 @@ from unittest.mock import AsyncMock, patch
|
||||
from anthropic import APITimeoutError, AuthenticationError, RateLimitError
|
||||
from freezegun import freeze_time
|
||||
from httpx import URL, Request, Response
|
||||
import pytest
|
||||
|
||||
from homeassistant.components import conversation
|
||||
from homeassistant.components.anthropic.const import DOMAIN
|
||||
@@ -21,11 +22,11 @@ from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
|
||||
|
||||
@patch("anthropic.resources.models.AsyncModels.list", new_callable=AsyncMock)
|
||||
@pytest.mark.usefixtures("mock_init_component")
|
||||
async def test_auth_error_handling(
|
||||
mock_model_list: AsyncMock,
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_init_component,
|
||||
mock_create_stream: AsyncMock,
|
||||
) -> None:
|
||||
"""Test reauth after authentication error during conversation."""
|
||||
@@ -64,11 +65,10 @@ async def test_auth_error_handling(
|
||||
|
||||
@freeze_time("2026-02-27 12:00:00")
|
||||
@patch("anthropic.resources.models.AsyncModels.list", new_callable=AsyncMock)
|
||||
@pytest.mark.usefixtures("mock_init_component")
|
||||
async def test_connection_error_handling(
|
||||
mock_model_list: AsyncMock,
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_init_component,
|
||||
mock_create_stream: AsyncMock,
|
||||
) -> None:
|
||||
"""Test making entity unavailable on connection error."""
|
||||
@@ -133,11 +133,11 @@ async def test_connection_error_handling(
|
||||
|
||||
|
||||
@patch("anthropic.resources.models.AsyncModels.list", new_callable=AsyncMock)
|
||||
@pytest.mark.usefixtures("mock_init_component")
|
||||
async def test_connection_check_reauth(
|
||||
mock_model_list: AsyncMock,
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_init_component,
|
||||
) -> None:
|
||||
"""Test authentication error during background availability check."""
|
||||
mock_model_list.side_effect = APITimeoutError(
|
||||
@@ -197,11 +197,10 @@ async def test_connection_check_reauth(
|
||||
|
||||
|
||||
@patch("anthropic.resources.models.AsyncModels.list", new_callable=AsyncMock)
|
||||
@pytest.mark.usefixtures("mock_init_component")
|
||||
async def test_connection_restore(
|
||||
mock_model_list: AsyncMock,
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_init_component,
|
||||
mock_create_stream: AsyncMock,
|
||||
) -> None:
|
||||
"""Test background availability check restore on non-connectivity error."""
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""Test Anthropic diagnostics."""
|
||||
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
@@ -9,11 +10,11 @@ from tests.components.diagnostics import get_diagnostics_for_config_entry
|
||||
from tests.typing import ClientSessionGenerator
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_init_component")
|
||||
async def test_entry_diagnostics(
|
||||
hass: HomeAssistant,
|
||||
hass_client: ClientSessionGenerator,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_init_component: None,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test config entry diagnostics."""
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
"""Tests for the Anthropic integration."""
|
||||
|
||||
from typing import Any
|
||||
from typing import TypedDict
|
||||
from unittest.mock import patch
|
||||
|
||||
from anthropic import (
|
||||
APIConnectionError,
|
||||
APIError,
|
||||
APITimeoutError,
|
||||
AuthenticationError,
|
||||
BadRequestError,
|
||||
@@ -37,12 +38,23 @@ from tests.common import MockConfigEntry
|
||||
MINOR_VERSION = AnthropicConfigFlow.MINOR_VERSION
|
||||
|
||||
|
||||
class ConversationSubentryExpectation(TypedDict):
|
||||
"""Expected registry state for a migrated conversation subentry."""
|
||||
|
||||
conversation_entity_id: str
|
||||
device_disabled_by: DeviceEntryDisabler | None
|
||||
entity_disabled_by: RegistryEntryDisabler | None
|
||||
device: int
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("side_effect", "error"),
|
||||
[
|
||||
(APIConnectionError(request=None), "Connection error"),
|
||||
(APITimeoutError(request=None), "Request timed out"),
|
||||
(
|
||||
pytest.param(
|
||||
APIConnectionError(request=None), "Connection error", id="connection_error"
|
||||
),
|
||||
pytest.param(APITimeoutError(request=None), "Request timed out", id="timeout"),
|
||||
pytest.param(
|
||||
BadRequestError(
|
||||
message=(
|
||||
"Your credit balance is too low to access"
|
||||
@@ -56,15 +68,16 @@ MINOR_VERSION = AnthropicConfigFlow.MINOR_VERSION
|
||||
body={"type": "error", "error": {"type": "invalid_request_error"}},
|
||||
),
|
||||
"Your credit balance is too low to access the Claude API",
|
||||
id="insufficient_credit",
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.usefixtures("mock_config_entry")
|
||||
async def test_init_error(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
side_effect,
|
||||
error,
|
||||
side_effect: APIError,
|
||||
error: str,
|
||||
) -> None:
|
||||
"""Test initialization errors."""
|
||||
with patch(
|
||||
@@ -96,10 +109,10 @@ async def test_init_auth_error(
|
||||
assert mock_config_entry.state is ConfigEntryState.SETUP_ERROR
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_init_component")
|
||||
async def test_init_repair_issue(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_init_component,
|
||||
issue_registry: ir.IssueRegistry,
|
||||
) -> None:
|
||||
"""Test that repair issue is created on deprecated model."""
|
||||
@@ -261,7 +274,7 @@ async def test_migration_from_v1_to_v2(
|
||||
"main_config_entry",
|
||||
),
|
||||
[
|
||||
(
|
||||
pytest.param(
|
||||
[ConfigEntryDisabler.USER, None],
|
||||
[DeviceEntryDisabler.CONFIG_ENTRY, None],
|
||||
[RegistryEntryDisabler.CONFIG_ENTRY, None],
|
||||
@@ -281,8 +294,9 @@ async def test_migration_from_v1_to_v2(
|
||||
},
|
||||
],
|
||||
1,
|
||||
id="first_entry_disabled",
|
||||
),
|
||||
(
|
||||
pytest.param(
|
||||
[None, ConfigEntryDisabler.USER],
|
||||
[None, DeviceEntryDisabler.CONFIG_ENTRY],
|
||||
[None, RegistryEntryDisabler.CONFIG_ENTRY],
|
||||
@@ -302,8 +316,9 @@ async def test_migration_from_v1_to_v2(
|
||||
},
|
||||
],
|
||||
0,
|
||||
id="second_entry_disabled",
|
||||
),
|
||||
(
|
||||
pytest.param(
|
||||
[ConfigEntryDisabler.USER, ConfigEntryDisabler.USER],
|
||||
[DeviceEntryDisabler.CONFIG_ENTRY, DeviceEntryDisabler.CONFIG_ENTRY],
|
||||
[RegistryEntryDisabler.CONFIG_ENTRY, RegistryEntryDisabler.CONFIG_ENTRY],
|
||||
@@ -323,6 +338,7 @@ async def test_migration_from_v1_to_v2(
|
||||
},
|
||||
],
|
||||
0,
|
||||
id="both_entries_disabled",
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -335,7 +351,7 @@ async def test_migration_from_v1_disabled(
|
||||
device_disabled_by: list[DeviceEntryDisabler | None],
|
||||
entity_disabled_by: list[RegistryEntryDisabler | None],
|
||||
merged_config_entry_disabled_by: ConfigEntryDisabler | None,
|
||||
conversation_subentry_data: list[dict[str, Any]],
|
||||
conversation_subentry_data: list[ConversationSubentryExpectation],
|
||||
main_config_entry: int,
|
||||
) -> None:
|
||||
"""Test migration where the config entries are disabled."""
|
||||
@@ -807,7 +823,7 @@ async def test_migration_from_v2_1_to_v2_2(
|
||||
),
|
||||
[
|
||||
# Config entry not disabled, update device and entity disabled by config entry
|
||||
(
|
||||
pytest.param(
|
||||
None,
|
||||
DeviceEntryDisabler.CONFIG_ENTRY,
|
||||
RegistryEntryDisabler.CONFIG_ENTRY,
|
||||
@@ -816,8 +832,9 @@ async def test_migration_from_v2_1_to_v2_2(
|
||||
None,
|
||||
DeviceEntryDisabler.USER,
|
||||
RegistryEntryDisabler.DEVICE,
|
||||
id="enabled_entry_stale_flags",
|
||||
),
|
||||
(
|
||||
pytest.param(
|
||||
None,
|
||||
DeviceEntryDisabler.USER,
|
||||
RegistryEntryDisabler.DEVICE,
|
||||
@@ -826,8 +843,9 @@ async def test_migration_from_v2_1_to_v2_2(
|
||||
None,
|
||||
DeviceEntryDisabler.USER,
|
||||
RegistryEntryDisabler.DEVICE,
|
||||
id="enabled_entry_device_disabled",
|
||||
),
|
||||
(
|
||||
pytest.param(
|
||||
None,
|
||||
DeviceEntryDisabler.USER,
|
||||
RegistryEntryDisabler.USER,
|
||||
@@ -836,8 +854,9 @@ async def test_migration_from_v2_1_to_v2_2(
|
||||
None,
|
||||
DeviceEntryDisabler.USER,
|
||||
RegistryEntryDisabler.USER,
|
||||
id="enabled_entry_entity_disabled",
|
||||
),
|
||||
(
|
||||
pytest.param(
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
@@ -846,9 +865,10 @@ async def test_migration_from_v2_1_to_v2_2(
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
id="enabled_entry_no_disabled_flags",
|
||||
),
|
||||
# Config entry disabled, migration does not run
|
||||
(
|
||||
pytest.param(
|
||||
ConfigEntryDisabler.USER,
|
||||
DeviceEntryDisabler.CONFIG_ENTRY,
|
||||
RegistryEntryDisabler.CONFIG_ENTRY,
|
||||
@@ -857,8 +877,9 @@ async def test_migration_from_v2_1_to_v2_2(
|
||||
ConfigEntryDisabler.USER,
|
||||
DeviceEntryDisabler.CONFIG_ENTRY,
|
||||
RegistryEntryDisabler.CONFIG_ENTRY,
|
||||
id="disabled_entry_config_entry_flags",
|
||||
),
|
||||
(
|
||||
pytest.param(
|
||||
ConfigEntryDisabler.USER,
|
||||
DeviceEntryDisabler.USER,
|
||||
RegistryEntryDisabler.DEVICE,
|
||||
@@ -867,8 +888,9 @@ async def test_migration_from_v2_1_to_v2_2(
|
||||
ConfigEntryDisabler.USER,
|
||||
DeviceEntryDisabler.USER,
|
||||
RegistryEntryDisabler.DEVICE,
|
||||
id="disabled_entry_device_disabled",
|
||||
),
|
||||
(
|
||||
pytest.param(
|
||||
ConfigEntryDisabler.USER,
|
||||
DeviceEntryDisabler.USER,
|
||||
RegistryEntryDisabler.USER,
|
||||
@@ -877,8 +899,9 @@ async def test_migration_from_v2_1_to_v2_2(
|
||||
ConfigEntryDisabler.USER,
|
||||
DeviceEntryDisabler.USER,
|
||||
RegistryEntryDisabler.USER,
|
||||
id="disabled_entry_entity_disabled",
|
||||
),
|
||||
(
|
||||
pytest.param(
|
||||
ConfigEntryDisabler.USER,
|
||||
None,
|
||||
None,
|
||||
@@ -887,6 +910,7 @@ async def test_migration_from_v2_1_to_v2_2(
|
||||
ConfigEntryDisabler.USER,
|
||||
None,
|
||||
None,
|
||||
id="disabled_entry_no_disabled_flags",
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -901,7 +925,7 @@ async def test_migrate_entry_to_v2_3(
|
||||
setup_result: bool,
|
||||
minor_version_after_migration: int,
|
||||
config_entry_disabled_by_after_migration: ConfigEntryDisabler | None,
|
||||
device_disabled_by_after_migration: ConfigEntryDisabler | None,
|
||||
device_disabled_by_after_migration: DeviceEntryDisabler | None,
|
||||
entity_disabled_by_after_migration: RegistryEntryDisabler | None,
|
||||
) -> None:
|
||||
"""Test migration to version 2.3."""
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
"""Tests for the Anthropic repairs flow."""
|
||||
|
||||
from types import SimpleNamespace
|
||||
from typing import Any
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
from anthropic.pagination import AsyncPage
|
||||
|
||||
from homeassistant.components.anthropic.const import CONF_CHAT_MODEL, DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntryState, ConfigSubentry
|
||||
from homeassistant.config_entries import (
|
||||
ConfigEntryState,
|
||||
ConfigSubentry,
|
||||
ConfigSubentryData,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResultType
|
||||
from homeassistant.helpers import issue_registry as ir
|
||||
@@ -25,7 +28,7 @@ def _make_entry(
|
||||
*,
|
||||
title: str,
|
||||
api_key: str,
|
||||
subentries_data: list[dict[str, Any]],
|
||||
subentries_data: list[ConfigSubentryData],
|
||||
) -> MockConfigEntry:
|
||||
"""Create a config entry with subentries and runtime data."""
|
||||
entry = MockConfigEntry(
|
||||
|
||||
Reference in New Issue
Block a user