async_setup_entry does entry.data[CONF_BASE_URL] as a hard index, but base_url is this fork's own addition: an entry created by a build from before the base_url commits (48195c9 era, version 2 minor 7) has no such key. On restart, setup raises KeyError: 'base_url' and the integration fails to load -- reproduced in test first, where async_setup returns False with that KeyError in the log. Fixed with a migration step rather than a defensive .get(), matching the pattern the rest of async_migrate_entry already uses, so the invariant "a loaded entry has base_url" holds for every caller instead of each call site guarding itself: - new step 2.7 -> 2.8 backfills DEFAULT_BASE_URL via dict.setdefault, so an entry that already has a base_url keeps its own endpoint and only a missing one is filled. An install from before this feature was implicitly using api.openai.com, which is exactly DEFAULT_BASE_URL, so the backfill preserves existing behavior rather than repointing anyone. - config_flow MINOR_VERSION 7 -> 8, otherwise the step never runs. test_client_built_with_base_url now also covers the preserve path, since its entry starts at minor 7 with a base_url set and must still reach the client unchanged after migrating. Also updated test_form's `minor_version == 7` assertion to 8; it pins the version new entries are created at, so the bump is expected. The reauth test's MockConfigEntry stays at minor 7 on purpose -- it stands in for an already-existing entry. Local: 138 passed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NxJYWgGJtCw13qUHZtG8ki
83 lines
2.9 KiB
Python
83 lines
2.9 KiB
Python
"""base_url must reach the OpenAI client, not just the config entry."""
|
|
|
|
from unittest.mock import AsyncMock, patch
|
|
|
|
from homeassistant.core import HomeAssistant
|
|
from pytest_homeassistant_custom_component.common import MockConfigEntry
|
|
|
|
from custom_components.openai_compatible.const import (
|
|
CONF_BASE_URL,
|
|
DEFAULT_BASE_URL,
|
|
DOMAIN,
|
|
)
|
|
|
|
|
|
async def test_client_built_with_base_url(hass: HomeAssistant) -> None:
|
|
"""async_setup_entry must pass base_url to AsyncOpenAI."""
|
|
entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
data={"api_key": "sk-test", CONF_BASE_URL: "http://192.0.2.10:4000/v1"},
|
|
version=2,
|
|
minor_version=7,
|
|
)
|
|
entry.add_to_hass(hass)
|
|
|
|
with patch(
|
|
"custom_components.openai_compatible.openai.AsyncOpenAI"
|
|
) as mock_client:
|
|
mock_client.return_value.platform_headers = lambda: {}
|
|
mock_client.return_value.with_options.return_value.models.list = AsyncMock()
|
|
await hass.config_entries.async_setup(entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
|
|
assert mock_client.call_args.kwargs["base_url"] == "http://192.0.2.10:4000/v1"
|
|
|
|
|
|
async def test_api_key_optional(hass: HomeAssistant) -> None:
|
|
"""An endpoint needing no auth should still set up."""
|
|
entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
data={CONF_BASE_URL: "http://192.0.2.10:4000/v1"},
|
|
version=2,
|
|
minor_version=7,
|
|
)
|
|
entry.add_to_hass(hass)
|
|
|
|
with patch(
|
|
"custom_components.openai_compatible.openai.AsyncOpenAI"
|
|
) as mock_client:
|
|
mock_client.return_value.platform_headers = lambda: {}
|
|
mock_client.return_value.with_options.return_value.models.list = AsyncMock()
|
|
await hass.config_entries.async_setup(entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
|
|
assert mock_client.call_args.kwargs["api_key"] == "not-required"
|
|
|
|
|
|
async def test_migration_backfills_missing_base_url(hass: HomeAssistant) -> None:
|
|
"""An entry stored before base_url existed must still set up.
|
|
|
|
Entries written by an earlier build of this fork have no base_url key at
|
|
all, so anything that indexes entry.data[CONF_BASE_URL] raises KeyError
|
|
and the integration fails to load on restart.
|
|
"""
|
|
entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
data={"api_key": "sk-test"},
|
|
version=2,
|
|
minor_version=7,
|
|
)
|
|
entry.add_to_hass(hass)
|
|
|
|
with patch(
|
|
"custom_components.openai_compatible.openai.AsyncOpenAI"
|
|
) as mock_client:
|
|
mock_client.return_value.platform_headers = lambda: {}
|
|
mock_client.return_value.with_options.return_value.models.list = AsyncMock()
|
|
assert await hass.config_entries.async_setup(entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
|
|
assert entry.data[CONF_BASE_URL] == DEFAULT_BASE_URL
|
|
assert entry.minor_version == 8
|
|
assert mock_client.call_args.kwargs["base_url"] == DEFAULT_BASE_URL
|