Add NYT Games integration (#126449)
* Add NYT Games integration * Add NYT Games integration * Add NYT Games integration * Add NYT Games integration * Add testpull/126561/head
parent
8eb76ea68d
commit
1d94e66b9c
|
@ -1024,6 +1024,8 @@ build.json @home-assistant/supervisor
|
|||
/tests/components/nut/ @bdraco @ollo69 @pestevez
|
||||
/homeassistant/components/nws/ @MatthewFlamm @kamiyo
|
||||
/tests/components/nws/ @MatthewFlamm @kamiyo
|
||||
/homeassistant/components/nyt_games/ @joostlek
|
||||
/tests/components/nyt_games/ @joostlek
|
||||
/homeassistant/components/nzbget/ @chriscla
|
||||
/tests/components/nzbget/ @chriscla
|
||||
/homeassistant/components/obihai/ @dshokouhi @ejpenney
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
"""The NYT Games integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from nyt_games import NYTGamesClient
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_TOKEN, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .coordinator import NYTGamesCoordinator
|
||||
|
||||
PLATFORMS: list[Platform] = [
|
||||
Platform.SENSOR,
|
||||
]
|
||||
|
||||
|
||||
type NYTGamesConfigEntry = ConfigEntry[NYTGamesCoordinator]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: NYTGamesConfigEntry) -> bool:
|
||||
"""Set up NYTGames from a config entry."""
|
||||
|
||||
client = NYTGamesClient(
|
||||
entry.data[CONF_TOKEN], session=async_get_clientsession(hass)
|
||||
)
|
||||
|
||||
coordinator = NYTGamesCoordinator(hass, client)
|
||||
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
entry.runtime_data = coordinator
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: NYTGamesConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
|
@ -0,0 +1,42 @@
|
|||
"""Config flow for NYT Games."""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from nyt_games import NYTGamesAuthenticationError, NYTGamesClient, NYTGamesError
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
||||
from homeassistant.const import CONF_TOKEN
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
|
||||
class NYTGamesConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""NYT Games config flow."""
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle a flow initialized by the user."""
|
||||
errors: dict[str, str] = {}
|
||||
if user_input:
|
||||
session = async_get_clientsession(self.hass)
|
||||
client = NYTGamesClient(user_input[CONF_TOKEN], session=session)
|
||||
try:
|
||||
latest_stats = await client.get_latest_stats()
|
||||
except NYTGamesAuthenticationError:
|
||||
errors["base"] = "invalid_auth"
|
||||
except NYTGamesError:
|
||||
errors["base"] = "cannot_connect"
|
||||
except Exception: # noqa: BLE001
|
||||
errors["base"] = "unknown"
|
||||
else:
|
||||
await self.async_set_unique_id(str(latest_stats.user_id))
|
||||
self._abort_if_unique_id_configured()
|
||||
return self.async_create_entry(title="NYT Games", data=user_input)
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
data_schema=vol.Schema({vol.Required(CONF_TOKEN): str}),
|
||||
errors=errors,
|
||||
)
|
|
@ -0,0 +1,7 @@
|
|||
"""Constants for the NYT Games integration."""
|
||||
|
||||
import logging
|
||||
|
||||
DOMAIN = "nyt_games"
|
||||
|
||||
LOGGER = logging.getLogger(__package__)
|
|
@ -0,0 +1,38 @@
|
|||
"""Define an object to manage fetching NYT Games data."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from nyt_games import NYTGamesClient, NYTGamesError, Wordle
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import LOGGER
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import NYTGamesConfigEntry
|
||||
|
||||
|
||||
class NYTGamesCoordinator(DataUpdateCoordinator[Wordle]):
|
||||
"""Class to manage fetching NYT Games data."""
|
||||
|
||||
config_entry: NYTGamesConfigEntry
|
||||
|
||||
def __init__(self, hass: HomeAssistant, client: NYTGamesClient) -> None:
|
||||
"""Initialize coordinator."""
|
||||
super().__init__(
|
||||
hass,
|
||||
logger=LOGGER,
|
||||
name="NYT Games",
|
||||
update_interval=timedelta(minutes=15),
|
||||
)
|
||||
self.client = client
|
||||
|
||||
async def _async_update_data(self) -> Wordle:
|
||||
try:
|
||||
return (await self.client.get_latest_stats()).stats.wordle
|
||||
except NYTGamesError as error:
|
||||
raise UpdateFailed(error) from error
|
|
@ -0,0 +1,21 @@
|
|||
"""Base class for NYT Games entities."""
|
||||
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import NYTGamesCoordinator
|
||||
|
||||
|
||||
class NYTGamesEntity(CoordinatorEntity[NYTGamesCoordinator]):
|
||||
"""Defines a base NYT Games entity."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(self, coordinator: NYTGamesCoordinator) -> None:
|
||||
"""Initialize a NYT Games entity."""
|
||||
super().__init__(coordinator)
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, str(coordinator.config_entry.unique_id))},
|
||||
manufacturer="New York Times",
|
||||
)
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"entity": {
|
||||
"sensor": {
|
||||
"wordles_played": {
|
||||
"default": "mdi:text-long"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"domain": "nyt_games",
|
||||
"name": "NYT Games",
|
||||
"codeowners": ["@joostlek"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/nyt_games",
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"requirements": ["nyt_games==0.3.0"]
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
"""Support for NYT Games sensors."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
|
||||
from nyt_games import Wordle
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import StateType
|
||||
|
||||
from . import NYTGamesConfigEntry
|
||||
from .coordinator import NYTGamesCoordinator
|
||||
from .entity import NYTGamesEntity
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class NYTGamesWordleSensorEntityDescription(SensorEntityDescription):
|
||||
"""Describes a NYT Games Wordle sensor entity."""
|
||||
|
||||
value_fn: Callable[[Wordle], StateType]
|
||||
|
||||
|
||||
SENSOR_TYPES: tuple[NYTGamesWordleSensorEntityDescription, ...] = (
|
||||
NYTGamesWordleSensorEntityDescription(
|
||||
key="wordles_played",
|
||||
translation_key="wordles_played",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement="games",
|
||||
value_fn=lambda wordle: wordle.games_played,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: NYTGamesConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up NYT Games sensor entities based on a config entry."""
|
||||
|
||||
coordinator = entry.runtime_data
|
||||
|
||||
async_add_entities(
|
||||
NYTGamesSensor(coordinator, description) for description in SENSOR_TYPES
|
||||
)
|
||||
|
||||
|
||||
class NYTGamesSensor(NYTGamesEntity, SensorEntity):
|
||||
"""Defines a NYT Games sensor."""
|
||||
|
||||
entity_description: NYTGamesWordleSensorEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: NYTGamesCoordinator,
|
||||
description: NYTGamesWordleSensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize NYT Games sensor."""
|
||||
super().__init__(coordinator)
|
||||
self.entity_description = description
|
||||
self._attr_unique_id = f"{coordinator.config_entry.unique_id}-{description.key}"
|
||||
|
||||
@property
|
||||
def native_value(self) -> StateType:
|
||||
"""Return the state of the sensor."""
|
||||
return self.entity_description.value_fn(self.coordinator.data)
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"token": "Token"
|
||||
},
|
||||
"data_description": {
|
||||
"token": "The NYT Games NYT-S cookie value."
|
||||
}
|
||||
}
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]"
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"sensor": {
|
||||
"wordles_played": {
|
||||
"name": "Wordles played"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -408,6 +408,7 @@ FLOWS = {
|
|||
"nuki",
|
||||
"nut",
|
||||
"nws",
|
||||
"nyt_games",
|
||||
"nzbget",
|
||||
"obihai",
|
||||
"octoprint",
|
||||
|
|
|
@ -4221,6 +4221,12 @@
|
|||
"config_flow": false,
|
||||
"iot_class": "local_push"
|
||||
},
|
||||
"nyt_games": {
|
||||
"name": "NYT Games",
|
||||
"integration_type": "service",
|
||||
"config_flow": true,
|
||||
"iot_class": "cloud_polling"
|
||||
},
|
||||
"nzbget": {
|
||||
"name": "NZBGet",
|
||||
"integration_type": "hub",
|
||||
|
|
|
@ -1483,6 +1483,9 @@ numato-gpio==0.13.0
|
|||
# homeassistant.components.trend
|
||||
numpy==1.26.0
|
||||
|
||||
# homeassistant.components.nyt_games
|
||||
nyt_games==0.3.0
|
||||
|
||||
# homeassistant.components.oasa_telematics
|
||||
oasatelematics==0.3
|
||||
|
||||
|
|
|
@ -1231,6 +1231,9 @@ numato-gpio==0.13.0
|
|||
# homeassistant.components.trend
|
||||
numpy==1.26.0
|
||||
|
||||
# homeassistant.components.nyt_games
|
||||
nyt_games==0.3.0
|
||||
|
||||
# homeassistant.components.google
|
||||
oauth2client==4.1.3
|
||||
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
"""Tests for the NYT Games integration."""
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def setup_integration(hass: HomeAssistant, config_entry: MockConfigEntry) -> None:
|
||||
"""Fixture for setting up the component."""
|
||||
config_entry.add_to_hass(hass)
|
||||
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
|
@ -0,0 +1,54 @@
|
|||
"""NYTGames tests configuration."""
|
||||
|
||||
from collections.abc import Generator
|
||||
from unittest.mock import patch
|
||||
|
||||
from nyt_games.models import LatestData
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.nyt_games.const import DOMAIN
|
||||
from homeassistant.const import CONF_TOKEN
|
||||
|
||||
from tests.common import MockConfigEntry, load_fixture
|
||||
from tests.components.smhi.common import AsyncMock
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_setup_entry() -> Generator[AsyncMock]:
|
||||
"""Override async_setup_entry."""
|
||||
with patch(
|
||||
"homeassistant.components.nyt_games.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry:
|
||||
yield mock_setup_entry
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_nyt_games_client() -> Generator[AsyncMock]:
|
||||
"""Mock an NYTGames client."""
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.nyt_games.NYTGamesClient",
|
||||
autospec=True,
|
||||
) as mock_client,
|
||||
patch(
|
||||
"homeassistant.components.nyt_games.config_flow.NYTGamesClient",
|
||||
new=mock_client,
|
||||
),
|
||||
):
|
||||
client = mock_client.return_value
|
||||
client.get_latest_stats.return_value = LatestData.from_json(
|
||||
load_fixture("latest.json", DOMAIN)
|
||||
).player
|
||||
yield client
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_config_entry() -> MockConfigEntry:
|
||||
"""Mock a config entry."""
|
||||
return MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
title="NYTGames",
|
||||
data={CONF_TOKEN: "token"},
|
||||
unique_id="218886794",
|
||||
)
|
|
@ -0,0 +1,69 @@
|
|||
{
|
||||
"states": [],
|
||||
"user_id": 218886794,
|
||||
"player": {
|
||||
"user_id": 218886794,
|
||||
"last_updated": 1726831978,
|
||||
"stats": {
|
||||
"spelling_bee": {
|
||||
"puzzles_started": 87,
|
||||
"total_words": 362,
|
||||
"total_pangrams": 15,
|
||||
"longest_word": {
|
||||
"word": "checkable",
|
||||
"center_letter": "b",
|
||||
"print_date": "2024-07-27"
|
||||
},
|
||||
"ranks": {
|
||||
"Beginner": 23,
|
||||
"Good": 21,
|
||||
"Good Start": 14,
|
||||
"Moving Up": 16,
|
||||
"Nice": 4,
|
||||
"Solid": 9
|
||||
}
|
||||
},
|
||||
"wordle": {
|
||||
"legacyStats": {
|
||||
"gamesPlayed": 70,
|
||||
"gamesWon": 51,
|
||||
"guesses": {
|
||||
"1": 0,
|
||||
"2": 1,
|
||||
"3": 7,
|
||||
"4": 11,
|
||||
"5": 20,
|
||||
"6": 12,
|
||||
"fail": 19
|
||||
},
|
||||
"currentStreak": 1,
|
||||
"maxStreak": 5,
|
||||
"lastWonDayOffset": 1189,
|
||||
"hasPlayed": true,
|
||||
"autoOptInTimestamp": 1708273168957,
|
||||
"hasMadeStatsChoice": false,
|
||||
"timestamp": 1726831978
|
||||
},
|
||||
"calculatedStats": {
|
||||
"gamesPlayed": 33,
|
||||
"gamesWon": 26,
|
||||
"guesses": {
|
||||
"1": 0,
|
||||
"2": 1,
|
||||
"3": 4,
|
||||
"4": 7,
|
||||
"5": 10,
|
||||
"6": 4,
|
||||
"fail": 7
|
||||
},
|
||||
"currentStreak": 1,
|
||||
"maxStreak": 5,
|
||||
"lastWonPrintDate": "2024-09-20",
|
||||
"lastCompletedPrintDate": "2024-09-20",
|
||||
"hasPlayed": true,
|
||||
"generation": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
# serializer version: 1
|
||||
# name: test_device_info
|
||||
DeviceRegistryEntrySnapshot({
|
||||
'area_id': None,
|
||||
'config_entries': <ANY>,
|
||||
'configuration_url': None,
|
||||
'connections': set({
|
||||
}),
|
||||
'disabled_by': None,
|
||||
'entry_type': None,
|
||||
'hw_version': None,
|
||||
'id': <ANY>,
|
||||
'identifiers': set({
|
||||
tuple(
|
||||
'nyt_games',
|
||||
'218886794',
|
||||
),
|
||||
}),
|
||||
'is_new': False,
|
||||
'labels': set({
|
||||
}),
|
||||
'manufacturer': 'New York Times',
|
||||
'model': None,
|
||||
'model_id': None,
|
||||
'name': 'NYTGames',
|
||||
'name_by_user': None,
|
||||
'primary_config_entry': <ANY>,
|
||||
'serial_number': None,
|
||||
'suggested_area': None,
|
||||
'sw_version': None,
|
||||
'via_device_id': None,
|
||||
})
|
||||
# ---
|
|
@ -0,0 +1,51 @@
|
|||
# serializer version: 1
|
||||
# name: test_all_entities[sensor.nytgames_wordles_played-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.nytgames_wordles_played',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Wordles played',
|
||||
'platform': 'nyt_games',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'wordles_played',
|
||||
'unique_id': '218886794-wordles_played',
|
||||
'unit_of_measurement': 'games',
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[sensor.nytgames_wordles_played-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'NYTGames Wordles played',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': 'games',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.nytgames_wordles_played',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '33',
|
||||
})
|
||||
# ---
|
|
@ -0,0 +1,104 @@
|
|||
"""Tests for the NYT Games config flow."""
|
||||
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from nyt_games import NYTGamesAuthenticationError, NYTGamesError
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.nyt_games.const import DOMAIN
|
||||
from homeassistant.config_entries import SOURCE_USER
|
||||
from homeassistant.const import CONF_TOKEN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResultType
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def test_full_flow(
|
||||
hass: HomeAssistant,
|
||||
mock_nyt_games_client: AsyncMock,
|
||||
mock_setup_entry: AsyncMock,
|
||||
) -> None:
|
||||
"""Test full flow."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_USER},
|
||||
)
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "user"
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_TOKEN: "token"},
|
||||
)
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result["title"] == "NYT Games"
|
||||
assert result["data"] == {CONF_TOKEN: "token"}
|
||||
assert result["result"].unique_id == "218886794"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("exception", "error"),
|
||||
[
|
||||
(NYTGamesAuthenticationError, "invalid_auth"),
|
||||
(NYTGamesError, "cannot_connect"),
|
||||
(Exception, "unknown"),
|
||||
],
|
||||
)
|
||||
async def test_flow_errors(
|
||||
hass: HomeAssistant,
|
||||
mock_nyt_games_client: AsyncMock,
|
||||
mock_setup_entry: AsyncMock,
|
||||
exception: Exception,
|
||||
error: str,
|
||||
) -> None:
|
||||
"""Test flow errors."""
|
||||
mock_nyt_games_client.get_latest_stats.side_effect = exception
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_USER},
|
||||
)
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "user"
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_TOKEN: "token"},
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["errors"] == {"base": error}
|
||||
|
||||
mock_nyt_games_client.get_latest_stats.side_effect = None
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_TOKEN: "token"},
|
||||
)
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
|
||||
|
||||
async def test_duplicate(
|
||||
hass: HomeAssistant,
|
||||
mock_nyt_games_client: AsyncMock,
|
||||
mock_setup_entry: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test duplicate flow."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_USER},
|
||||
)
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "user"
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{CONF_TOKEN: "token"},
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "already_configured"
|
|
@ -0,0 +1,29 @@
|
|||
"""Tests for the NYT Games integration."""
|
||||
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.nyt_games.const import DOMAIN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
|
||||
from . import setup_integration
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def test_device_info(
|
||||
hass: HomeAssistant,
|
||||
snapshot: SnapshotAssertion,
|
||||
mock_nyt_games_client: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
) -> None:
|
||||
"""Test device registry integration."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
device_entry = device_registry.async_get_device(
|
||||
identifiers={(DOMAIN, mock_config_entry.unique_id)}
|
||||
)
|
||||
assert device_entry is not None
|
||||
assert device_entry == snapshot
|
|
@ -0,0 +1,55 @@
|
|||
"""Tests for the NYT Games sensor platform."""
|
||||
|
||||
from datetime import timedelta
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
from nyt_games import NYTGamesError
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
from homeassistant.const import STATE_UNAVAILABLE
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from . import setup_integration
|
||||
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform
|
||||
|
||||
|
||||
async def test_all_entities(
|
||||
hass: HomeAssistant,
|
||||
snapshot: SnapshotAssertion,
|
||||
mock_nyt_games_client: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test all entities."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
|
||||
|
||||
|
||||
async def test_updating_exception(
|
||||
hass: HomeAssistant,
|
||||
mock_nyt_games_client: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Test handling an exception during update."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
mock_nyt_games_client.get_latest_stats.side_effect = NYTGamesError
|
||||
|
||||
freezer.tick(timedelta(minutes=15))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get("sensor.nytgames_wordles_played").state == STATE_UNAVAILABLE
|
||||
|
||||
mock_nyt_games_client.get_latest_stats.side_effect = None
|
||||
|
||||
freezer.tick(timedelta(minutes=15))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get("sensor.nytgames_wordles_played").state != STATE_UNAVAILABLE
|
Loading…
Reference in New Issue