Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
"services": {
"add_bonus_time": {
"service": "mdi:timer-plus-outline"
},
"update_pin_code": {
"service": "mdi:lock-open-outline"
}
}
}
47 changes: 46 additions & 1 deletion homeassistant/components/nintendo_parental_controls/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import voluptuous as vol

from homeassistant.const import ATTR_DEVICE_ID
from homeassistant.const import ATTR_DEVICE_ID, CONF_PIN
from homeassistant.core import HomeAssistant, ServiceCall, callback
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import config_validation as cv, device_registry as dr
Expand All @@ -20,6 +20,7 @@ class NintendoParentalServices(StrEnum):
"""Store keys for Nintendo Parental services."""

ADD_BONUS_TIME = "add_bonus_time"
UPDATE_PIN_CODE = "update_pin_code"


@callback
Expand All @@ -38,6 +39,17 @@ def async_setup_services(
}
),
)
hass.services.async_register(
domain=DOMAIN,
service=NintendoParentalServices.UPDATE_PIN_CODE,
service_func=async_update_pin_code,
schema=vol.Schema(
{
vol.Required(ATTR_DEVICE_ID): cv.string,
vol.Required(CONF_PIN): cv.string,
}
Comment thread
pantherale0 marked this conversation as resolved.
),
)


def _get_nintendo_device_id(dev: dr.DeviceEntry) -> str | None:
Expand Down Expand Up @@ -73,3 +85,36 @@ async def async_add_bonus_time(call: ServiceCall) -> None:
translation_domain=DOMAIN,
translation_key="invalid_device",
)


async def async_update_pin_code(call: ServiceCall) -> None:
"""Update the PIN code for a device."""
config_entry: NintendoParentalControlsConfigEntry | None = None
data = call.data
device_id: str = data[ATTR_DEVICE_ID]
new_pin: str = data[CONF_PIN]
if len(new_pin) < 4 or len(new_pin) > 8:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="invalid_pin_length",
)
device = dr.async_get(call.hass).async_get(device_id)
if device is None:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="device_not_found",
)
for entry_id in device.config_entries:
entry = call.hass.config_entries.async_get_entry(entry_id)
if entry is not None and entry.domain == DOMAIN:
config_entry = entry
break
nintendo_device_id = _get_nintendo_device_id(device)
Comment thread
pantherale0 marked this conversation as resolved.
Comment thread
pantherale0 marked this conversation as resolved.
if config_entry and nintendo_device_id:
return await config_entry.runtime_data.api.devices[
nintendo_device_id
].set_new_pin(new_pin)
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="invalid_device",
)
14 changes: 14 additions & 0 deletions homeassistant/components/nintendo_parental_controls/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,17 @@ add_bonus_time:
selector:
device:
integration: nintendo_parental_controls
update_pin_code:
fields:
pin:
required: true
example: 1234
selector:
text:
type: password
device_id:
required: true
example: 1234567890abcdef1234567890abcdef
selector:
device:
integration: nintendo_parental_controls
18 changes: 18 additions & 0 deletions homeassistant/components/nintendo_parental_controls/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@
"invalid_device": {
"message": "The specified device is not a Nintendo device."
},
"invalid_pin_length": {
"message": "The PIN code must be between 4 and 8 characters long."
},
"no_devices_found": {
"message": "No Nintendo devices found for this account."
},
Expand All @@ -112,6 +115,21 @@
}
},
"name": "Add Bonus Time"
},
"update_pin_code": {
"description": "Update the PIN code for the selected Nintendo Switch.",
"fields": {
"device_id": {
"description": "The ID of the device to update the PIN code for.",
"example": "1234567890abcdef",
"name": "Device"
Comment on lines +122 to +125
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Device IDs are generated and therefore the examples will always be incorrect regardless the length

},
"pin": {
"description": "The new PIN code. Accepted range: 1000-99999999.",
"name": "PIN"
}
},
"name": "Update PIN Code"
}
}
}
91 changes: 77 additions & 14 deletions tests/components/nintendo_parental_controls/test_services.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Test Nintendo Parental Controls service calls."""

from typing import Any
from unittest.mock import AsyncMock

import pytest
Expand All @@ -11,7 +12,7 @@
from homeassistant.components.nintendo_parental_controls.services import (
NintendoParentalServices,
)
from homeassistant.const import ATTR_DEVICE_ID
from homeassistant.const import ATTR_DEVICE_ID, CONF_PIN
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import device_registry as dr
Expand Down Expand Up @@ -44,34 +45,73 @@ async def test_add_bonus_time(
assert len(mock_nintendo_device.add_extra_time.mock_calls) == 1


async def test_add_bonus_time_invalid_device(
@pytest.mark.parametrize(
("service", "payload", "exception_key"),
[
(
NintendoParentalServices.ADD_BONUS_TIME,
{ATTR_DEVICE_ID: "invalid_device", ATTR_BONUS_TIME: 15},
"device_not_found",
),
(
NintendoParentalServices.UPDATE_PIN_CODE,
{ATTR_DEVICE_ID: "invalid_device", CONF_PIN: "1234"},
"device_not_found",
),
(
NintendoParentalServices.UPDATE_PIN_CODE,
{ATTR_DEVICE_ID: "invalid_device", CONF_PIN: "123"},
"invalid_pin_length",
),
(
NintendoParentalServices.UPDATE_PIN_CODE,
{ATTR_DEVICE_ID: "invalid_device", CONF_PIN: "123456789"},
"invalid_pin_length",
),
],
)
async def test_service_no_device_exceptions(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_nintendo_client: AsyncMock,
service: NintendoParentalServices,
payload: dict[str, Any],
exception_key: str,
) -> None:
"""Test add bonus time service."""
"""Test service exceptions."""
await setup_integration(hass, mock_config_entry)
with pytest.raises(ServiceValidationError) as err:
await hass.services.async_call(
DOMAIN,
NintendoParentalServices.ADD_BONUS_TIME,
{
ATTR_DEVICE_ID: "invalid_device_id",
ATTR_BONUS_TIME: 15,
},
service,
payload,
blocking=True,
)
assert err.value.translation_domain == DOMAIN
assert err.value.translation_key == "device_not_found"
assert err.value.translation_key == exception_key


async def test_add_bonus_time_device_id_not_nintendo(
@pytest.mark.parametrize(
("service", "payload", "exception_key"),
[
(
NintendoParentalServices.ADD_BONUS_TIME,
{ATTR_BONUS_TIME: 15},
"invalid_device",
),
(NintendoParentalServices.UPDATE_PIN_CODE, {CONF_PIN: 1234}, "invalid_device"),
],
)
async def test_service_invalid_device_exceptions(
hass: HomeAssistant,
device_registry: dr.DeviceRegistry,
mock_config_entry: MockConfigEntry,
mock_nintendo_client: AsyncMock,
service: NintendoParentalServices,
payload: dict[str, Any],
exception_key: str,
) -> None:
"""Test add bonus time service with a device that is not a valid Nintendo device."""
"""Test service exceptions with a device that is not a valid Nintendo device."""
await setup_integration(hass, mock_config_entry)
# Create a device that does not have a Nintendo identifier
device_entry = device_registry.async_get_or_create(
Expand All @@ -81,12 +121,35 @@ async def test_add_bonus_time_device_id_not_nintendo(
with pytest.raises(ServiceValidationError) as err:
await hass.services.async_call(
DOMAIN,
NintendoParentalServices.ADD_BONUS_TIME,
service,
{
**payload,
ATTR_DEVICE_ID: device_entry.id,
ATTR_BONUS_TIME: 15,
},
blocking=True,
)
assert err.value.translation_domain == DOMAIN
assert err.value.translation_key == "invalid_device"
assert err.value.translation_key == exception_key


async def test_update_pin_code(
hass: HomeAssistant,
device_registry: dr.DeviceRegistry,
mock_config_entry: MockConfigEntry,
mock_nintendo_client: AsyncMock,
mock_nintendo_device: AsyncMock,
) -> None:
"""Test update pin code service."""
await setup_integration(hass, mock_config_entry)
device_entry = device_registry.async_get_device(identifiers={(DOMAIN, "testdevid")})
assert device_entry
await hass.services.async_call(
DOMAIN,
NintendoParentalServices.UPDATE_PIN_CODE,
{
ATTR_DEVICE_ID: device_entry.id,
CONF_PIN: "1234",
},
blocking=True,
)
assert len(mock_nintendo_device.set_new_pin.mock_calls) == 1