Compare commits

...

11 Commits

Author SHA1 Message Date
Robert Resch
6af30fb8f5 Merge branch 'dev' into gj-20251001-06 2026-01-20 14:36:15 +00:00
G Johansson
0ffb5fcfac Change slightly 2025-10-24 21:17:46 +00:00
G Johansson
a51fba80cc Fixes 2025-10-24 20:30:25 +00:00
G Johansson
fe39037477 Fix 2025-10-24 20:30:25 +00:00
G Johansson
d860c13559 upb 2025-10-24 20:30:25 +00:00
G Johansson
9f41cc0024 Add migration comment 2025-10-24 20:30:21 +00:00
G Johansson
659846548b some more 2025-10-24 20:29:48 +00:00
G Johansson
cdf781dc01 Fixes 2025-10-24 20:29:31 +00:00
G Johansson
c78570558d Fix some 2025-10-24 20:25:17 +00:00
G Johansson
b5d1becb3b Fix docstring 2025-10-24 20:25:17 +00:00
G Johansson
0656642efb Non string config entry unique id will now raise 2025-10-24 20:25:16 +00:00
28 changed files with 101 additions and 213 deletions

View File

@@ -35,7 +35,7 @@ class ArveConfigFlowHandler(ConfigFlow, domain=DOMAIN):
except ArveConnectionError:
errors["base"] = "cannot_connect"
else:
await self.async_set_unique_id(customer.customerId)
await self.async_set_unique_id(str(customer.customerId))
self._abort_if_unique_id_configured()
return self.async_create_entry(
title="Arve",

View File

@@ -54,13 +54,9 @@ async def async_migrate_entry(hass: HomeAssistant, entry: LaundrifyConfigEntry)
_LOGGER.debug("Migrating from version %s", entry.version)
if entry.version == 1:
# 1 -> 2: Unique ID from integer to string
if entry.minor_version == 1:
minor_version = 2
hass.config_entries.async_update_entry(
entry, unique_id=str(entry.unique_id), minor_version=minor_version
)
if entry.version == 1 and entry.minor_version == 1:
# Migration of unique id was removed in #153369
hass.config_entries.async_update_entry(entry, minor_version=2)
_LOGGER.debug("Migration successful")

View File

@@ -47,7 +47,7 @@ class OAuth2FlowHandler(
self.logger.exception("Unexpected error")
return self.async_abort(reason="unknown")
await self.async_set_unique_id(current_user.id)
await self.async_set_unique_id(str(current_user.id))
if self.source != SOURCE_REAUTH:
self._abort_if_unique_id_configured()
return self.async_create_entry(

View File

@@ -51,7 +51,7 @@ class MonzoFlowHandler(
"""Create an entry for the flow."""
self.oauth_data = data
user_id = data[CONF_TOKEN]["user_id"]
await self.async_set_unique_id(user_id)
await self.async_set_unique_id(str(user_id))
if self.source != SOURCE_REAUTH:
self._abort_if_unique_id_configured()
else:

View File

@@ -93,13 +93,9 @@ async def async_migrate_entry(hass: HomeAssistant, entry: NexiaConfigEntry) -> b
_LOGGER.debug("Migrating from version %s", entry.version)
if entry.version == 1:
# 1 -> 2: Unique ID from integer to string
if entry.minor_version == 1:
minor_version = 2
hass.config_entries.async_update_entry(
entry, unique_id=str(entry.unique_id), minor_version=minor_version
)
if entry.version == 1 and entry.minor_version == 1:
# Migration of unique id was removed in #153369
hass.config_entries.async_update_entry(entry, minor_version=2)
_LOGGER.debug("Migration successful")

View File

@@ -71,12 +71,9 @@ async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
_LOGGER.debug("Migrating from version %s", entry.version)
if entry.version == 1:
if entry.minor_version == 1:
minor_version = 2
hass.config_entries.async_update_entry(
entry, unique_id=str(entry.unique_id), minor_version=minor_version
)
if entry.version == 1 and entry.minor_version == 1:
# Migration of unique id was removed in #153369
hass.config_entries.async_update_entry(entry, minor_version=2)
_LOGGER.debug("Migration successful")

View File

@@ -92,7 +92,7 @@ class ToonFlowHandler(AbstractOAuth2FlowHandler, domain=DOMAIN):
if self.migrate_entry:
await self.hass.config_entries.async_remove(self.migrate_entry)
await self.async_set_unique_id(agreement.agreement_id)
await self.async_set_unique_id(str(agreement.agreement_id))
self._abort_if_unique_id_configured()
self.data[CONF_AGREEMENT_ID] = agreement.agreement_id

View File

@@ -74,13 +74,9 @@ async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
_LOGGER.debug("Migrating from version %s", entry.version)
if entry.version == 1:
# 1 -> 2: Unique ID from integer to string
if entry.minor_version == 1:
minor_version = 2
hass.config_entries.async_update_entry(
entry, unique_id=str(entry.unique_id), minor_version=minor_version
)
if entry.version == 1 and entry.minor_version == 1:
# Migration of unique id was removed in #153369
hass.config_entries.async_update_entry(entry, minor_version=2)
_LOGGER.debug("Migration successful")

View File

@@ -11,7 +11,6 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.httpx_client import create_async_httpx_client
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
@@ -132,23 +131,7 @@ async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Migrate old entry."""
# convert unique_id to string
if entry.version == 1 and entry.minor_version == 1:
if isinstance(entry.unique_id, int):
hass.config_entries.async_update_entry(
entry, unique_id=str(entry.unique_id)
)
device_registry = dr.async_get(hass)
for device in dr.async_entries_for_config_entry(
device_registry, entry.entry_id
):
new_identifiers = set()
for identifier in device.identifiers:
if identifier[0] == DOMAIN:
new_identifiers.add((DOMAIN, str(identifier[1])))
else:
new_identifiers.add(identifier)
device_registry.async_update_device(
device.id, new_identifiers=new_identifiers
)
# Migration of unique id was removed in #153369
hass.config_entries.async_update_entry(entry, minor_version=2)
return True

View File

@@ -8,7 +8,6 @@ from collections.abc import (
Callable,
Coroutine,
Generator,
Hashable,
Iterable,
Mapping,
ValuesView,
@@ -1833,7 +1832,14 @@ class ConfigEntryItems(UserDict[str, ConfigEntry]):
def __setitem__(self, entry_id: str, entry: ConfigEntry) -> None:
"""Add an item."""
data = self.data
self.check_unique_id(entry)
if not self.check_unique_id(entry):
# If unique id not valid, don't save entry.
_LOGGER.error(
"The entry %s unique id %s is not a string and will not be added",
entry.title,
entry.unique_id,
)
return
if entry_id in data:
# This is likely a bug in a test that is adding the same entry twice.
# In the future, once we have fixed the tests, this will raise HomeAssistantError.
@@ -1842,45 +1848,27 @@ class ConfigEntryItems(UserDict[str, ConfigEntry]):
data[entry_id] = entry
self._index_entry(entry)
def check_unique_id(self, entry: ConfigEntry) -> None:
"""Check config entry unique id.
def check_unique_id(self, entry: ConfigEntry) -> bool:
"""Check if config entry unique id is valid and log if not.
For a string unique id (this is the correct case): return
For a hashable non string unique id: log warning
For a non-hashable unique id: raise error
Args:
entry: Config entry.
Returns:
True if the unique id is a string.
"""
if (unique_id := entry.unique_id) is None:
return
if isinstance(unique_id, str):
# Unique id should be a string
return
if isinstance(unique_id, Hashable): # type: ignore[unreachable]
# Checks for other non-string was added in HA Core 2024.10
# In HA Core 2025.10, we should remove the error and instead fail
report_issue = async_suggest_report_issue(
self._hass, integration_domain=entry.domain
)
_LOGGER.error(
(
"Config entry '%s' from integration %s has an invalid unique_id"
" '%s' of type %s when a string is expected, please %s"
),
entry.title,
entry.domain,
entry.unique_id,
type(entry.unique_id).__name__,
report_issue,
)
else:
# Guard against integrations using unhashable unique_id
# In HA Core 2024.11, the guard was changed from warning to failing
raise HomeAssistantError(
f"The entry unique id {unique_id} is not a string."
)
return True
if not isinstance(unique_id, str):
return False # type: ignore[unreachable]
return True
def _index_entry(self, entry: ConfigEntry) -> None:
"""Index an entry."""
self.check_unique_id(entry)
if not self.check_unique_id(entry):
raise HomeAssistantError(
f"Cannot update unique id to {entry.unique_id} as it's not a string value."
)
self._domain_index.setdefault(entry.domain, []).append(entry)
if entry.unique_id is not None:
self._domain_unique_id_index.setdefault(entry.domain, {}).setdefault(
@@ -1913,7 +1901,10 @@ class ConfigEntryItems(UserDict[str, ConfigEntry]):
"""
entry_id = entry.entry_id
self._unindex_entry(entry_id)
self.check_unique_id(entry)
if not self.check_unique_id(entry):
raise HomeAssistantError(
f"Cannot update unique id to {new_unique_id} as it's not a string value."
)
object.__setattr__(entry, "unique_id", new_unique_id)
self._index_entry(entry)
entry.clear_state_cache()
@@ -1929,7 +1920,7 @@ class ConfigEntryItems(UserDict[str, ConfigEntry]):
"""Get entry by domain and unique id."""
if unique_id is None:
return None # type: ignore[unreachable]
if not isinstance(unique_id, Hashable):
if not isinstance(unique_id, str):
raise HomeAssistantError(
f"The entry unique id {unique_id} is not a string."
)

View File

@@ -27,7 +27,10 @@ def mock_setup_entry() -> Generator[AsyncMock]:
def mock_config_entry(hass: HomeAssistant, mock_arve: MagicMock) -> MockConfigEntry:
"""Return the default mocked config entry."""
return MockConfigEntry(
title="Arve", domain=DOMAIN, data=USER_INPUT, unique_id=mock_arve.customer_id
title="Arve",
domain=DOMAIN,
data=USER_INPUT,
unique_id=str(mock_arve.customer_id),
)

View File

@@ -34,7 +34,7 @@ async def test_correct_flow(
assert result2["type"] is FlowResultType.CREATE_ENTRY
assert result2["data"] == USER_INPUT
assert len(mock_setup_entry.mock_calls) == 1
assert result2["result"].unique_id == 12345
assert result2["result"].unique_id == "12345"
async def test_form_cannot_connect(

View File

@@ -516,8 +516,8 @@ async def test_camera_content_type(
"framerate": 2,
"verify_ssl": True,
}
await help_setup_mock_config_entry(hass, cam_config_jpg, unique_id=12345)
await help_setup_mock_config_entry(hass, cam_config_svg, unique_id=54321)
await help_setup_mock_config_entry(hass, cam_config_jpg, unique_id="12345")
await help_setup_mock_config_entry(hass, cam_config_svg, unique_id="54321")
client = await hass_client()

View File

@@ -40,7 +40,7 @@ async def laundrify_setup_config_entry(
"""Create laundrify entry in Home Assistant."""
entry = MockConfigEntry(
domain=DOMAIN,
unique_id=VALID_ACCOUNT_ID,
unique_id=str(VALID_ACCOUNT_ID),
data={CONF_ACCESS_TOKEN: access_token},
minor_version=2,
)

View File

@@ -65,7 +65,7 @@ async def test_migrate_entry_minor_version_1_2(hass: HomeAssistant) -> None:
data={CONF_ACCESS_TOKEN: VALID_ACCESS_TOKEN},
version=1,
minor_version=1,
unique_id=123456,
unique_id="123456",
)
entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry.entry_id)

View File

@@ -59,7 +59,7 @@ def mock_config_entry(expires_at: int, scopes: list[str]) -> MockConfigEntry:
return MockConfigEntry(
domain=DOMAIN,
title=TITLE,
unique_id=54321,
unique_id="54321",
data={
"auth_implementation": DOMAIN,
"token": {

View File

@@ -74,7 +74,7 @@ async def test_full_flow(
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["title"] == "test@microbees.com"
assert "result" in result
assert result["result"].unique_id == 54321
assert result["result"].unique_id == "54321"
assert "token" in result["result"].data
assert result["result"].data["token"]["access_token"] == "mock-access-token"
assert result["result"].data["token"]["refresh_token"] == "mock-refresh-token"

View File

@@ -62,7 +62,7 @@ async def test_migrate_entry_minor_version_1_2(hass: HomeAssistant) -> None:
data={CONF_USERNAME: "mock", CONF_PASSWORD: "mock"},
version=1,
minor_version=1,
unique_id=123456,
unique_id="123456",
)
entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry.entry_id)

View File

@@ -20,7 +20,6 @@ from .conftest import (
HOST,
MAC_ADDRESS_UNIQUE_ID,
PASSWORD,
SERIAL_NUMBER,
SERIAL_RESPONSE,
URL,
WIFI_PARAMS_RESPONSE,
@@ -190,13 +189,6 @@ async def test_multiple_config_entries(
],
CONFIG_ENTRY_DATA,
),
# Old unique id with serial, but same host
(
SERIAL_NUMBER,
CONFIG_ENTRY_DATA,
[mock_response(SERIAL_RESPONSE), mock_json_response(WIFI_PARAMS_RESPONSE)],
CONFIG_ENTRY_DATA,
),
# Old unique id with no serial, but same host
(
None,
@@ -220,7 +212,6 @@ async def test_multiple_config_entries(
],
ids=[
"duplicate-mac-unique-id",
"duplicate-host-legacy-serial-number",
"duplicate-host-port-no-serial",
"duplicate-duplicate-hostname",
],

View File

@@ -238,30 +238,6 @@ async def test_fix_unique_id_duplicate(
"expected_device_identifier",
),
[
(
SERIAL_NUMBER,
SERIAL_NUMBER,
SERIAL_NUMBER,
str(SERIAL_NUMBER),
MAC_ADDRESS_UNIQUE_ID,
MAC_ADDRESS_UNIQUE_ID,
),
(
SERIAL_NUMBER,
SERIAL_NUMBER,
f"{SERIAL_NUMBER}-rain-delay",
f"{SERIAL_NUMBER}-1",
f"{MAC_ADDRESS_UNIQUE_ID}-rain-delay",
f"{MAC_ADDRESS_UNIQUE_ID}-1",
),
(
SERIAL_NUMBER,
SERIAL_NUMBER,
SERIAL_NUMBER,
SERIAL_NUMBER,
MAC_ADDRESS_UNIQUE_ID,
MAC_ADDRESS_UNIQUE_ID,
),
("0", 0, "0", "0", MAC_ADDRESS_UNIQUE_ID, MAC_ADDRESS_UNIQUE_ID),
(
"0",
@@ -289,9 +265,6 @@ async def test_fix_unique_id_duplicate(
),
],
ids=(
"serial-number",
"serial-number-with-suffix",
"serial-number-int",
"zero-serial",
"zero-serial-suffix",
"new-format",

View File

@@ -213,7 +213,7 @@ async def test_agreement_already_set_up(
) -> None:
"""Test showing display form again if display already exists."""
await setup_component(hass)
MockConfigEntry(domain=DOMAIN, unique_id=123).add_to_hass(hass)
MockConfigEntry(domain=DOMAIN, unique_id="123").add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
)
@@ -312,7 +312,7 @@ async def test_import_migration(
aioclient_mock: AiohttpClientMocker,
) -> None:
"""Test if importing step with migration works."""
old_entry = MockConfigEntry(domain=DOMAIN, unique_id=123, version=1)
old_entry = MockConfigEntry(domain=DOMAIN, unique_id="123", version=1)
old_entry.add_to_hass(hass)
await setup_component(hass)

View File

@@ -5,7 +5,7 @@ from __future__ import annotations
import copy
from datetime import timedelta
from typing import Any
from unittest.mock import AsyncMock, MagicMock, PropertyMock, patch
from unittest.mock import AsyncMock, PropertyMock, patch
from freezegun.api import FrozenDateTimeFactory
from kasa import (
@@ -85,7 +85,7 @@ async def test_configuring_tplink_causes_discovery(
patch("homeassistant.components.tplink.Discover.discover_single"),
patch("homeassistant.components.tplink.Device.connect"),
):
discover.return_value = {MagicMock(): MagicMock()}
discover.return_value = {}
await async_setup_component(hass, tplink.DOMAIN, {tplink.DOMAIN: {}})
await hass.async_block_till_done(wait_background_tasks=True)
# call_count will differ based on number of broadcast addresses

View File

@@ -121,7 +121,7 @@ async def test_max_regions(hass: HomeAssistant) -> None:
for i in range(5):
MockConfigEntry(
domain=DOMAIN,
unique_id=i,
unique_id=str(i),
).add_to_hass(hass)
result = await hass.config_entries.flow.async_init(

View File

@@ -16,7 +16,7 @@ async def test_migrate_entry_minor_version_1_2(hass: HomeAssistant) -> None:
data={"protocol": "TCP", "address": "1.2.3.4", "file_path": "upb.upe"},
version=1,
minor_version=1,
unique_id=123456,
unique_id="123456",
)
entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(entry.entry_id)

View File

@@ -4,7 +4,7 @@ from unittest.mock import patch
from httpx import RequestError
from homeassistant.components.wolflink.const import DEVICE_ID, DOMAIN, MANUFACTURER
from homeassistant.components.wolflink.const import DEVICE_ID, DOMAIN
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr
@@ -18,27 +18,10 @@ async def test_unique_id_migration(
) -> None:
"""Test already configured while creating entry."""
config_entry = MockConfigEntry(
domain=DOMAIN, unique_id=CONFIG[DEVICE_ID], data=CONFIG
domain=DOMAIN, unique_id=str(CONFIG[DEVICE_ID]), data=CONFIG
)
config_entry.add_to_hass(hass)
device_id = device_registry.async_get_or_create(
config_entry_id=config_entry.entry_id,
identifiers={(DOMAIN, CONFIG[DEVICE_ID])},
configuration_url="https://www.wolf-smartset.com/",
manufacturer=MANUFACTURER,
).id
assert config_entry.version == 1
assert config_entry.minor_version == 1
assert config_entry.unique_id == 1234
assert (
hass.config_entries.async_entry_for_domain_unique_id(DOMAIN, 1234)
is config_entry
)
assert hass.config_entries.async_entry_for_domain_unique_id(DOMAIN, "1234") is None
assert device_registry.async_get(device_id).identifiers == {(DOMAIN, 1234)}
with (
patch(
"homeassistant.components.wolflink.fetch_parameters",
@@ -54,6 +37,3 @@ async def test_unique_id_migration(
hass.config_entries.async_entry_for_domain_unique_id(DOMAIN, "1234")
is config_entry
)
assert hass.config_entries.async_entry_for_domain_unique_id(DOMAIN, 1234) is None
assert device_registry.async_get(device_id).identifiers == {(DOMAIN, "1234")}

View File

@@ -2195,7 +2195,7 @@ async def test_addon_running_already_configured(
"lr_s2_authenticated_key": "old321",
},
title=TITLE,
unique_id=1234, # Unique ID is purposely set to int to test migration logic
unique_id="1234",
)
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)

View File

@@ -41,7 +41,7 @@ async def test_remove_stale_devices(
"""Test removing devices with old-format ids."""
config_entry = MockConfigEntry(
unique_id=uuid.uuid4(),
unique_id=str(uuid.uuid4()),
domain="zwave_me",
data={CONF_TOKEN: "test_token", CONF_URL: "http://test_test"},
)

View File

@@ -7172,11 +7172,11 @@ async def test_reconfigure_subentry_create_subentry(hass: HomeAssistant) -> None
}
@pytest.mark.parametrize("unique_id", [["blah", "bleh"], {"key": "value"}])
async def test_unhashable_unique_id_fails(
@pytest.mark.parametrize("unique_id", [["blah", "bleh"], {"key": "value"}, 123, 2.3])
async def test_non_string_unique_id_fails(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture, unique_id: Any
) -> None:
"""Test the ConfigEntryItems user dict fails unhashable unique_id."""
"""Test the ConfigEntryItems user dict fails non string unique_id."""
entries = config_entries.ConfigEntryItems(hass)
entry = config_entries.ConfigEntry(
data={},
@@ -7193,11 +7193,8 @@ async def test_unhashable_unique_id_fails(
)
unique_id_string = re.escape(str(unique_id))
with pytest.raises(
HomeAssistantError,
match=f"The entry unique id {unique_id_string} is not a string.",
):
entries[entry.entry_id] = entry
entries[entry.entry_id] = entry
assert f"The entry title unique id {unique_id} is not a string" in caplog.text
assert entry.entry_id not in entries
@@ -7208,11 +7205,11 @@ async def test_unhashable_unique_id_fails(
entries.get_entry_by_domain_and_unique_id("test", unique_id)
@pytest.mark.parametrize("unique_id", [["blah", "bleh"], {"key": "value"}])
async def test_unhashable_unique_id_fails_on_update(
@pytest.mark.parametrize("unique_id", [["blah", "bleh"], {"key": "value"}, 123, 2.3])
async def test_non_string_unique_id_fails_on_update(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture, unique_id: Any
) -> None:
"""Test the ConfigEntryItems user dict fails non-hashable unique_id on update."""
"""Test the ConfigEntryItems user dict fails non-string unique_id on update."""
entries = config_entries.ConfigEntryItems(hass)
entry = config_entries.ConfigEntry(
data={},
@@ -7234,7 +7231,7 @@ async def test_unhashable_unique_id_fails_on_update(
unique_id_string = re.escape(str(unique_id))
with pytest.raises(
HomeAssistantError,
match=f"The entry unique id {unique_id_string} is not a string.",
match=f"Cannot update unique id to {unique_id_string} as it's not a string value.",
):
entries.update_unique_id(entry, unique_id)
@@ -7273,48 +7270,33 @@ async def test_string_unique_id_no_warning(
assert entries.get_entry_by_domain_and_unique_id("test", "123") is None
@pytest.mark.parametrize(
("unique_id", "type_name"),
[
(123, "int"),
(2.3, "float"),
],
)
async def test_hashable_unique_id(
hass: HomeAssistant,
caplog: pytest.LogCaptureFixture,
unique_id: Any,
type_name: str,
async def test_loading_incorrect_unique_silently_ignored(
hass: HomeAssistant, hass_storage: dict[str, Any], caplog: pytest.LogCaptureFixture
) -> None:
"""Test the ConfigEntryItems user dict handles hashable non string unique_id."""
entries = config_entries.ConfigEntryItems(hass)
entry = config_entries.ConfigEntry(
data={},
discovery_keys={},
domain="test",
entry_id="mock_id",
minor_version=1,
options={},
source="test",
subentries_data=(),
title="title",
unique_id=unique_id,
version=1,
)
"""Test entry with incorrect unique id silently is ignored."""
hass_storage[config_entries.STORAGE_KEY] = {
"version": 1,
"data": {
"entries": [
{
"version": 5,
"domain": "my_domain",
"entry_id": "mock-id",
"data": {"my": "data"},
"source": "user",
"title": "Mock title",
"unique_id": 123,
"system_options": {"disable_new_entities": True},
}
]
},
}
manager = config_entries.ConfigEntries(hass, {})
await manager.async_initialize()
entries[entry.entry_id] = entry
assert (
"Config entry 'title' from integration test has an invalid unique_id"
f" '{unique_id}' of type {type_name} when a string is expected"
) in caplog.text
assert entry.entry_id in entries
assert entries[entry.entry_id] is entry
assert entries.get_entry_by_domain_and_unique_id("test", unique_id) == entry
del entries[entry.entry_id]
assert not entries
assert entries.get_entry_by_domain_and_unique_id("test", unique_id) is None
entries = manager.async_entries()
assert len(entries) == 0
assert "The entry unique id 123 is not a string" not in caplog.text
async def test_no_unique_id_no_warning(