mirror of
https://github.com/home-assistant/core.git
synced 2025-07-27 23:27:37 +00:00
Add update reward action to Habitica integration (#139157)
This commit is contained in:
parent
694a77fe3c
commit
2509353221
@ -35,6 +35,10 @@ ATTR_TYPE = "type"
|
|||||||
ATTR_PRIORITY = "priority"
|
ATTR_PRIORITY = "priority"
|
||||||
ATTR_TAG = "tag"
|
ATTR_TAG = "tag"
|
||||||
ATTR_KEYWORD = "keyword"
|
ATTR_KEYWORD = "keyword"
|
||||||
|
ATTR_REMOVE_TAG = "remove_tag"
|
||||||
|
ATTR_ALIAS = "alias"
|
||||||
|
ATTR_PRIORITY = "priority"
|
||||||
|
ATTR_COST = "cost"
|
||||||
|
|
||||||
SERVICE_CAST_SKILL = "cast_skill"
|
SERVICE_CAST_SKILL = "cast_skill"
|
||||||
SERVICE_START_QUEST = "start_quest"
|
SERVICE_START_QUEST = "start_quest"
|
||||||
@ -50,6 +54,7 @@ SERVICE_SCORE_REWARD = "score_reward"
|
|||||||
|
|
||||||
SERVICE_TRANSFORMATION = "transformation"
|
SERVICE_TRANSFORMATION = "transformation"
|
||||||
|
|
||||||
|
SERVICE_UPDATE_REWARD = "update_reward"
|
||||||
|
|
||||||
DEVELOPER_ID = "4c4ca53f-c059-4ffa-966e-9d29dd405daf"
|
DEVELOPER_ID = "4c4ca53f-c059-4ffa-966e-9d29dd405daf"
|
||||||
X_CLIENT = f"{DEVELOPER_ID} - {APPLICATION_NAME} {__version__}"
|
X_CLIENT = f"{DEVELOPER_ID} - {APPLICATION_NAME} {__version__}"
|
||||||
|
@ -217,6 +217,13 @@
|
|||||||
"sections": {
|
"sections": {
|
||||||
"filter": "mdi:calendar-filter"
|
"filter": "mdi:calendar-filter"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"update_reward": {
|
||||||
|
"service": "mdi:treasure-chest",
|
||||||
|
"sections": {
|
||||||
|
"tag_options": "mdi:tag",
|
||||||
|
"developer_options": "mdi:test-tube"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,8 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from dataclasses import asdict
|
from dataclasses import asdict
|
||||||
import logging
|
import logging
|
||||||
from typing import TYPE_CHECKING, Any
|
from typing import TYPE_CHECKING, Any, cast
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
from aiohttp import ClientError
|
from aiohttp import ClientError
|
||||||
from habiticalib import (
|
from habiticalib import (
|
||||||
@ -13,6 +14,7 @@ from habiticalib import (
|
|||||||
NotAuthorizedError,
|
NotAuthorizedError,
|
||||||
NotFoundError,
|
NotFoundError,
|
||||||
Skill,
|
Skill,
|
||||||
|
Task,
|
||||||
TaskData,
|
TaskData,
|
||||||
TaskPriority,
|
TaskPriority,
|
||||||
TaskType,
|
TaskType,
|
||||||
@ -20,6 +22,7 @@ from habiticalib import (
|
|||||||
)
|
)
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.components.todo import ATTR_DESCRIPTION, ATTR_RENAME
|
||||||
from homeassistant.config_entries import ConfigEntryState
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
from homeassistant.const import ATTR_NAME, CONF_NAME
|
from homeassistant.const import ATTR_NAME, CONF_NAME
|
||||||
from homeassistant.core import (
|
from homeassistant.core import (
|
||||||
@ -34,14 +37,17 @@ from homeassistant.helpers.issue_registry import IssueSeverity, async_create_iss
|
|||||||
from homeassistant.helpers.selector import ConfigEntrySelector
|
from homeassistant.helpers.selector import ConfigEntrySelector
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
|
ATTR_ALIAS,
|
||||||
ATTR_ARGS,
|
ATTR_ARGS,
|
||||||
ATTR_CONFIG_ENTRY,
|
ATTR_CONFIG_ENTRY,
|
||||||
|
ATTR_COST,
|
||||||
ATTR_DATA,
|
ATTR_DATA,
|
||||||
ATTR_DIRECTION,
|
ATTR_DIRECTION,
|
||||||
ATTR_ITEM,
|
ATTR_ITEM,
|
||||||
ATTR_KEYWORD,
|
ATTR_KEYWORD,
|
||||||
ATTR_PATH,
|
ATTR_PATH,
|
||||||
ATTR_PRIORITY,
|
ATTR_PRIORITY,
|
||||||
|
ATTR_REMOVE_TAG,
|
||||||
ATTR_SKILL,
|
ATTR_SKILL,
|
||||||
ATTR_TAG,
|
ATTR_TAG,
|
||||||
ATTR_TARGET,
|
ATTR_TARGET,
|
||||||
@ -61,6 +67,7 @@ from .const import (
|
|||||||
SERVICE_SCORE_REWARD,
|
SERVICE_SCORE_REWARD,
|
||||||
SERVICE_START_QUEST,
|
SERVICE_START_QUEST,
|
||||||
SERVICE_TRANSFORMATION,
|
SERVICE_TRANSFORMATION,
|
||||||
|
SERVICE_UPDATE_REWARD,
|
||||||
)
|
)
|
||||||
from .coordinator import HabiticaConfigEntry
|
from .coordinator import HabiticaConfigEntry
|
||||||
|
|
||||||
@ -104,6 +111,21 @@ SERVICE_TRANSFORMATION_SCHEMA = vol.Schema(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
SERVICE_UPDATE_TASK_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(ATTR_CONFIG_ENTRY): ConfigEntrySelector(),
|
||||||
|
vol.Required(ATTR_TASK): cv.string,
|
||||||
|
vol.Optional(ATTR_RENAME): cv.string,
|
||||||
|
vol.Optional(ATTR_DESCRIPTION): cv.string,
|
||||||
|
vol.Optional(ATTR_TAG): vol.All(cv.ensure_list, [str]),
|
||||||
|
vol.Optional(ATTR_REMOVE_TAG): vol.All(cv.ensure_list, [str]),
|
||||||
|
vol.Optional(ATTR_ALIAS): vol.All(
|
||||||
|
cv.string, cv.matches_regex("^[a-zA-Z0-9-_]*$")
|
||||||
|
),
|
||||||
|
vol.Optional(ATTR_COST): vol.Coerce(float),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
SERVICE_GET_TASKS_SCHEMA = vol.Schema(
|
SERVICE_GET_TASKS_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Required(ATTR_CONFIG_ENTRY): ConfigEntrySelector({"integration": DOMAIN}),
|
vol.Required(ATTR_CONFIG_ENTRY): ConfigEntrySelector({"integration": DOMAIN}),
|
||||||
@ -516,6 +538,130 @@ def async_setup_services(hass: HomeAssistant) -> None: # noqa: C901
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
async def update_task(call: ServiceCall) -> ServiceResponse:
|
||||||
|
"""Update task action."""
|
||||||
|
entry = get_config_entry(hass, call.data[ATTR_CONFIG_ENTRY])
|
||||||
|
coordinator = entry.runtime_data
|
||||||
|
await coordinator.async_refresh()
|
||||||
|
|
||||||
|
try:
|
||||||
|
current_task = next(
|
||||||
|
task
|
||||||
|
for task in coordinator.data.tasks
|
||||||
|
if call.data[ATTR_TASK] in (str(task.id), task.alias, task.text)
|
||||||
|
and task.Type is TaskType.REWARD
|
||||||
|
)
|
||||||
|
except StopIteration as e:
|
||||||
|
raise ServiceValidationError(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="task_not_found",
|
||||||
|
translation_placeholders={"task": f"'{call.data[ATTR_TASK]}'"},
|
||||||
|
) from e
|
||||||
|
|
||||||
|
task_id = current_task.id
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
assert task_id
|
||||||
|
data = Task()
|
||||||
|
|
||||||
|
if rename := call.data.get(ATTR_RENAME):
|
||||||
|
data["text"] = rename
|
||||||
|
|
||||||
|
if (description := call.data.get(ATTR_DESCRIPTION)) is not None:
|
||||||
|
data["notes"] = description
|
||||||
|
|
||||||
|
tags = cast(list[str], call.data.get(ATTR_TAG))
|
||||||
|
remove_tags = cast(list[str], call.data.get(ATTR_REMOVE_TAG))
|
||||||
|
|
||||||
|
if tags or remove_tags:
|
||||||
|
update_tags = set(current_task.tags)
|
||||||
|
user_tags = {
|
||||||
|
tag.name.lower(): tag.id
|
||||||
|
for tag in coordinator.data.user.tags
|
||||||
|
if tag.id and tag.name
|
||||||
|
}
|
||||||
|
|
||||||
|
if tags:
|
||||||
|
# Creates new tag if it doesn't exist
|
||||||
|
async def create_tag(tag_name: str) -> UUID:
|
||||||
|
tag_id = (await coordinator.habitica.create_tag(tag_name)).data.id
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
assert tag_id
|
||||||
|
return tag_id
|
||||||
|
|
||||||
|
try:
|
||||||
|
update_tags.update(
|
||||||
|
{
|
||||||
|
user_tags.get(tag_name.lower())
|
||||||
|
or (await create_tag(tag_name))
|
||||||
|
for tag_name in tags
|
||||||
|
}
|
||||||
|
)
|
||||||
|
except TooManyRequestsError as e:
|
||||||
|
raise HomeAssistantError(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="setup_rate_limit_exception",
|
||||||
|
translation_placeholders={"retry_after": str(e.retry_after)},
|
||||||
|
) from e
|
||||||
|
except HabiticaException as e:
|
||||||
|
raise HomeAssistantError(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="service_call_exception",
|
||||||
|
translation_placeholders={"reason": str(e.error.message)},
|
||||||
|
) from e
|
||||||
|
except ClientError as e:
|
||||||
|
raise HomeAssistantError(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="service_call_exception",
|
||||||
|
translation_placeholders={"reason": str(e)},
|
||||||
|
) from e
|
||||||
|
|
||||||
|
if remove_tags:
|
||||||
|
update_tags.difference_update(
|
||||||
|
{
|
||||||
|
user_tags[tag_name.lower()]
|
||||||
|
for tag_name in remove_tags
|
||||||
|
if tag_name.lower() in user_tags
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
data["tags"] = list(update_tags)
|
||||||
|
|
||||||
|
if (alias := call.data.get(ATTR_ALIAS)) is not None:
|
||||||
|
data["alias"] = alias
|
||||||
|
|
||||||
|
if (cost := call.data.get(ATTR_COST)) is not None:
|
||||||
|
data["value"] = cost
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = await coordinator.habitica.update_task(task_id, data)
|
||||||
|
except TooManyRequestsError as e:
|
||||||
|
raise HomeAssistantError(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="setup_rate_limit_exception",
|
||||||
|
translation_placeholders={"retry_after": str(e.retry_after)},
|
||||||
|
) from e
|
||||||
|
except HabiticaException as e:
|
||||||
|
raise HomeAssistantError(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="service_call_exception",
|
||||||
|
translation_placeholders={"reason": str(e.error.message)},
|
||||||
|
) from e
|
||||||
|
except ClientError as e:
|
||||||
|
raise HomeAssistantError(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="service_call_exception",
|
||||||
|
translation_placeholders={"reason": str(e)},
|
||||||
|
) from e
|
||||||
|
else:
|
||||||
|
return response.data.to_dict(omit_none=True)
|
||||||
|
|
||||||
|
hass.services.async_register(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_UPDATE_REWARD,
|
||||||
|
update_task,
|
||||||
|
schema=SERVICE_UPDATE_TASK_SCHEMA,
|
||||||
|
supports_response=SupportsResponse.ONLY,
|
||||||
|
)
|
||||||
hass.services.async_register(
|
hass.services.async_register(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
SERVICE_API_CALL,
|
SERVICE_API_CALL,
|
||||||
|
@ -140,3 +140,43 @@ get_tasks:
|
|||||||
required: false
|
required: false
|
||||||
selector:
|
selector:
|
||||||
text:
|
text:
|
||||||
|
update_reward:
|
||||||
|
fields:
|
||||||
|
config_entry: *config_entry
|
||||||
|
task: *task
|
||||||
|
rename:
|
||||||
|
selector:
|
||||||
|
text:
|
||||||
|
description:
|
||||||
|
required: false
|
||||||
|
selector:
|
||||||
|
text:
|
||||||
|
multiline: true
|
||||||
|
cost:
|
||||||
|
required: false
|
||||||
|
selector:
|
||||||
|
number:
|
||||||
|
min: 0
|
||||||
|
step: 0.01
|
||||||
|
unit_of_measurement: "🪙"
|
||||||
|
mode: box
|
||||||
|
tag_options:
|
||||||
|
collapsed: true
|
||||||
|
fields:
|
||||||
|
tag:
|
||||||
|
required: false
|
||||||
|
selector:
|
||||||
|
text:
|
||||||
|
multiple: true
|
||||||
|
remove_tag:
|
||||||
|
required: false
|
||||||
|
selector:
|
||||||
|
text:
|
||||||
|
multiple: true
|
||||||
|
developer_options:
|
||||||
|
collapsed: true
|
||||||
|
fields:
|
||||||
|
alias:
|
||||||
|
required: false
|
||||||
|
selector:
|
||||||
|
text:
|
||||||
|
@ -7,7 +7,23 @@
|
|||||||
"unit_tasks": "tasks",
|
"unit_tasks": "tasks",
|
||||||
"unit_health_points": "HP",
|
"unit_health_points": "HP",
|
||||||
"unit_mana_points": "MP",
|
"unit_mana_points": "MP",
|
||||||
"unit_experience_points": "XP"
|
"unit_experience_points": "XP",
|
||||||
|
"config_entry_description": "Select the Habitica account to update a task.",
|
||||||
|
"task_description": "The name (or task ID) of the task you want to update.",
|
||||||
|
"rename_name": "Rename",
|
||||||
|
"rename_description": "The new title for the Habitica task.",
|
||||||
|
"description_name": "Update description",
|
||||||
|
"description_description": "The new description for the Habitica task.",
|
||||||
|
"tag_name": "Add tags",
|
||||||
|
"tag_description": "Add tags to the Habitica task. If a tag does not already exist, a new one will be created.",
|
||||||
|
"remove_tag_name": "Remove tags",
|
||||||
|
"remove_tag_description": "Remove tags from the Habitica task.",
|
||||||
|
"alias_name": "Task alias",
|
||||||
|
"alias_description": "A task alias can be used instead of the name or task ID. Only dashes, underscores, and alphanumeric characters are supported. The task alias must be unique among all your tasks.",
|
||||||
|
"developer_options_name": "Advanced settings",
|
||||||
|
"developer_options_description": "Additional features available in developer mode.",
|
||||||
|
"tag_options_name": "Tags",
|
||||||
|
"tag_options_description": "Add or remove tags from a task."
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"abort": {
|
"abort": {
|
||||||
@ -457,6 +473,12 @@
|
|||||||
},
|
},
|
||||||
"authentication_failed": {
|
"authentication_failed": {
|
||||||
"message": "Authentication failed. It looks like your API token has been reset. Please re-authenticate using your new token"
|
"message": "Authentication failed. It looks like your API token has been reset. Please re-authenticate using your new token"
|
||||||
|
},
|
||||||
|
"frequency_not_weekly": {
|
||||||
|
"message": "Unable to update task, weekly repeat settings apply only to weekly recurring dailies."
|
||||||
|
},
|
||||||
|
"frequency_not_monthly": {
|
||||||
|
"message": "Unable to update task, monthly repeat settings apply only to monthly recurring dailies."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"issues": {
|
"issues": {
|
||||||
@ -651,6 +673,54 @@
|
|||||||
"description": "Use the optional filters to narrow the returned tasks."
|
"description": "Use the optional filters to narrow the returned tasks."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"update_reward": {
|
||||||
|
"name": "Update a reward",
|
||||||
|
"description": "Updates a specific reward for the selected Habitica character",
|
||||||
|
"fields": {
|
||||||
|
"config_entry": {
|
||||||
|
"name": "[%key:component::habitica::common::config_entry_name%]",
|
||||||
|
"description": "Select the Habitica account to update a reward."
|
||||||
|
},
|
||||||
|
"task": {
|
||||||
|
"name": "[%key:component::habitica::common::task_name%]",
|
||||||
|
"description": "[%key:component::habitica::common::task_description%]"
|
||||||
|
},
|
||||||
|
"rename": {
|
||||||
|
"name": "[%key:component::habitica::common::rename_name%]",
|
||||||
|
"description": "[%key:component::habitica::common::rename_description%]"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"name": "[%key:component::habitica::common::description_name%]",
|
||||||
|
"description": "[%key:component::habitica::common::description_description%]"
|
||||||
|
},
|
||||||
|
"tag": {
|
||||||
|
"name": "[%key:component::habitica::common::tag_name%]",
|
||||||
|
"description": "[%key:component::habitica::common::tag_description%]"
|
||||||
|
},
|
||||||
|
"remove_tag": {
|
||||||
|
"name": "[%key:component::habitica::common::remove_tag_name%]",
|
||||||
|
"description": "[%key:component::habitica::common::remove_tag_description%]"
|
||||||
|
},
|
||||||
|
"alias": {
|
||||||
|
"name": "[%key:component::habitica::common::alias_name%]",
|
||||||
|
"description": "[%key:component::habitica::common::alias_description%]"
|
||||||
|
},
|
||||||
|
"cost": {
|
||||||
|
"name": "Cost",
|
||||||
|
"description": "Update the cost of a reward."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sections": {
|
||||||
|
"tag_options": {
|
||||||
|
"name": "[%key:component::habitica::common::tag_options_name%]",
|
||||||
|
"description": "[%key:component::habitica::common::tag_options_description%]"
|
||||||
|
},
|
||||||
|
"developer_options": {
|
||||||
|
"name": "[%key:component::habitica::common::developer_options_name%]",
|
||||||
|
"description": "[%key:component::habitica::common::developer_options_description%]"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"selector": {
|
"selector": {
|
||||||
|
@ -14,6 +14,7 @@ from habiticalib import (
|
|||||||
HabiticaResponse,
|
HabiticaResponse,
|
||||||
HabiticaScoreResponse,
|
HabiticaScoreResponse,
|
||||||
HabiticaSleepResponse,
|
HabiticaSleepResponse,
|
||||||
|
HabiticaTagResponse,
|
||||||
HabiticaTaskOrderResponse,
|
HabiticaTaskOrderResponse,
|
||||||
HabiticaTaskResponse,
|
HabiticaTaskResponse,
|
||||||
HabiticaTasksResponse,
|
HabiticaTasksResponse,
|
||||||
@ -144,6 +145,12 @@ async def mock_habiticalib() -> Generator[AsyncMock]:
|
|||||||
load_fixture("anonymized.json", DOMAIN)
|
load_fixture("anonymized.json", DOMAIN)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
client.update_task.return_value = HabiticaTaskResponse.from_json(
|
||||||
|
load_fixture("task.json", DOMAIN)
|
||||||
|
)
|
||||||
|
client.create_tag.return_value = HabiticaTagResponse.from_json(
|
||||||
|
load_fixture("create_tag.json", DOMAIN)
|
||||||
|
)
|
||||||
client.habitipy.return_value = {
|
client.habitipy.return_value = {
|
||||||
"tasks": {
|
"tasks": {
|
||||||
"user": {
|
"user": {
|
||||||
|
8
tests/components/habitica/fixtures/create_tag.json
Normal file
8
tests/components/habitica/fixtures/create_tag.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"name": "Home Assistant",
|
||||||
|
"id": "8bc0afbf-ab8e-49a4-982d-67a40557ed1a"
|
||||||
|
},
|
||||||
|
"notifications": []
|
||||||
|
}
|
27
tests/components/habitica/fixtures/reward.json
Normal file
27
tests/components/habitica/fixtures/reward.json
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"_id": "5e2ea1df-f6e6-4ba3-bccb-97c5ec63e99b",
|
||||||
|
"type": "reward",
|
||||||
|
"text": "Belohne Dich selbst",
|
||||||
|
"notes": "Schaue fern, spiele ein Spiel, gönne Dir einen Leckerbissen, es liegt ganz bei Dir!",
|
||||||
|
"tags": [],
|
||||||
|
"value": 10,
|
||||||
|
"priority": 1,
|
||||||
|
"attribute": "str",
|
||||||
|
"challenge": {},
|
||||||
|
"group": {
|
||||||
|
"completedBy": {},
|
||||||
|
"assignedUsers": []
|
||||||
|
},
|
||||||
|
"byHabitica": false,
|
||||||
|
"reminders": [],
|
||||||
|
"createdAt": "2024-07-07T17:51:53.266Z",
|
||||||
|
"updatedAt": "2024-07-07T17:51:53.266Z",
|
||||||
|
"userId": "5f359083-ef78-4af0-985a-0b2c6d05797c",
|
||||||
|
"id": "5e2ea1df-f6e6-4ba3-bccb-97c5ec63e99b"
|
||||||
|
},
|
||||||
|
"notifications": [],
|
||||||
|
"userV": 589,
|
||||||
|
"appVersion": "5.28.6"
|
||||||
|
}
|
@ -533,7 +533,10 @@
|
|||||||
"type": "reward",
|
"type": "reward",
|
||||||
"text": "Belohne Dich selbst",
|
"text": "Belohne Dich selbst",
|
||||||
"notes": "Schaue fern, spiele ein Spiel, gönne Dir einen Leckerbissen, es liegt ganz bei Dir!",
|
"notes": "Schaue fern, spiele ein Spiel, gönne Dir einen Leckerbissen, es liegt ganz bei Dir!",
|
||||||
"tags": [],
|
"tags": [
|
||||||
|
"3450351f-1323-4c7e-9fd2-0cdff25b3ce0",
|
||||||
|
"b2780f82-b3b5-49a3-a677-48f2c8c7e3bb"
|
||||||
|
],
|
||||||
"value": 10,
|
"value": 10,
|
||||||
"priority": 1,
|
"priority": 1,
|
||||||
"attribute": "str",
|
"attribute": "str",
|
||||||
|
@ -1271,6 +1271,10 @@
|
|||||||
'th': False,
|
'th': False,
|
||||||
'w': True,
|
'w': True,
|
||||||
}),
|
}),
|
||||||
|
'tags': list([
|
||||||
|
'3450351f-1323-4c7e-9fd2-0cdff25b3ce0',
|
||||||
|
'b2780f82-b3b5-49a3-a677-48f2c8c7e3bb',
|
||||||
|
]),
|
||||||
'text': 'Belohne Dich selbst',
|
'text': 'Belohne Dich selbst',
|
||||||
'type': 'reward',
|
'type': 'reward',
|
||||||
'value': 10.0,
|
'value': 10.0,
|
||||||
|
@ -1081,6 +1081,8 @@
|
|||||||
'startDate': None,
|
'startDate': None,
|
||||||
'streak': None,
|
'streak': None,
|
||||||
'tags': list([
|
'tags': list([
|
||||||
|
'3450351f-1323-4c7e-9fd2-0cdff25b3ce0',
|
||||||
|
'b2780f82-b3b5-49a3-a677-48f2c8c7e3bb',
|
||||||
]),
|
]),
|
||||||
'text': 'Belohne Dich selbst',
|
'text': 'Belohne Dich selbst',
|
||||||
'type': 'reward',
|
'type': 'reward',
|
||||||
@ -3321,6 +3323,8 @@
|
|||||||
'startDate': None,
|
'startDate': None,
|
||||||
'streak': None,
|
'streak': None,
|
||||||
'tags': list([
|
'tags': list([
|
||||||
|
'3450351f-1323-4c7e-9fd2-0cdff25b3ce0',
|
||||||
|
'b2780f82-b3b5-49a3-a677-48f2c8c7e3bb',
|
||||||
]),
|
]),
|
||||||
'text': 'Belohne Dich selbst',
|
'text': 'Belohne Dich selbst',
|
||||||
'type': 'reward',
|
'type': 'reward',
|
||||||
@ -5580,6 +5584,8 @@
|
|||||||
'startDate': None,
|
'startDate': None,
|
||||||
'streak': None,
|
'streak': None,
|
||||||
'tags': list([
|
'tags': list([
|
||||||
|
'3450351f-1323-4c7e-9fd2-0cdff25b3ce0',
|
||||||
|
'b2780f82-b3b5-49a3-a677-48f2c8c7e3bb',
|
||||||
]),
|
]),
|
||||||
'text': 'Belohne Dich selbst',
|
'text': 'Belohne Dich selbst',
|
||||||
'type': 'reward',
|
'type': 'reward',
|
||||||
@ -5954,6 +5960,8 @@
|
|||||||
'startDate': None,
|
'startDate': None,
|
||||||
'streak': None,
|
'streak': None,
|
||||||
'tags': list([
|
'tags': list([
|
||||||
|
'3450351f-1323-4c7e-9fd2-0cdff25b3ce0',
|
||||||
|
'b2780f82-b3b5-49a3-a677-48f2c8c7e3bb',
|
||||||
]),
|
]),
|
||||||
'text': 'Belohne Dich selbst',
|
'text': 'Belohne Dich selbst',
|
||||||
'type': 'reward',
|
'type': 'reward',
|
||||||
|
@ -6,16 +6,19 @@ from unittest.mock import AsyncMock, patch
|
|||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from aiohttp import ClientError
|
from aiohttp import ClientError
|
||||||
from habiticalib import Direction, Skill
|
from habiticalib import Direction, HabiticaTaskResponse, Skill, Task
|
||||||
import pytest
|
import pytest
|
||||||
from syrupy.assertion import SnapshotAssertion
|
from syrupy.assertion import SnapshotAssertion
|
||||||
|
|
||||||
from homeassistant.components.habitica.const import (
|
from homeassistant.components.habitica.const import (
|
||||||
|
ATTR_ALIAS,
|
||||||
ATTR_CONFIG_ENTRY,
|
ATTR_CONFIG_ENTRY,
|
||||||
|
ATTR_COST,
|
||||||
ATTR_DIRECTION,
|
ATTR_DIRECTION,
|
||||||
ATTR_ITEM,
|
ATTR_ITEM,
|
||||||
ATTR_KEYWORD,
|
ATTR_KEYWORD,
|
||||||
ATTR_PRIORITY,
|
ATTR_PRIORITY,
|
||||||
|
ATTR_REMOVE_TAG,
|
||||||
ATTR_SKILL,
|
ATTR_SKILL,
|
||||||
ATTR_TAG,
|
ATTR_TAG,
|
||||||
ATTR_TARGET,
|
ATTR_TARGET,
|
||||||
@ -33,7 +36,9 @@ from homeassistant.components.habitica.const import (
|
|||||||
SERVICE_SCORE_REWARD,
|
SERVICE_SCORE_REWARD,
|
||||||
SERVICE_START_QUEST,
|
SERVICE_START_QUEST,
|
||||||
SERVICE_TRANSFORMATION,
|
SERVICE_TRANSFORMATION,
|
||||||
|
SERVICE_UPDATE_REWARD,
|
||||||
)
|
)
|
||||||
|
from homeassistant.components.todo import ATTR_DESCRIPTION, ATTR_RENAME
|
||||||
from homeassistant.config_entries import ConfigEntryState
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
|
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
|
||||||
@ -45,7 +50,7 @@ from .conftest import (
|
|||||||
ERROR_TOO_MANY_REQUESTS,
|
ERROR_TOO_MANY_REQUESTS,
|
||||||
)
|
)
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry, load_fixture
|
||||||
|
|
||||||
REQUEST_EXCEPTION_MSG = "Unable to connect to Habitica: reason"
|
REQUEST_EXCEPTION_MSG = "Unable to connect to Habitica: reason"
|
||||||
RATE_LIMIT_EXCEPTION_MSG = "Rate limit exceeded, try again in 5 seconds"
|
RATE_LIMIT_EXCEPTION_MSG = "Rate limit exceeded, try again in 5 seconds"
|
||||||
@ -889,3 +894,261 @@ async def test_get_tasks(
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert response == snapshot
|
assert response == snapshot
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("exception", "expected_exception", "exception_msg"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
ERROR_TOO_MANY_REQUESTS,
|
||||||
|
HomeAssistantError,
|
||||||
|
RATE_LIMIT_EXCEPTION_MSG,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ERROR_BAD_REQUEST,
|
||||||
|
HomeAssistantError,
|
||||||
|
REQUEST_EXCEPTION_MSG,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ClientError,
|
||||||
|
HomeAssistantError,
|
||||||
|
"Unable to connect to Habitica: ",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
@pytest.mark.usefixtures("habitica")
|
||||||
|
async def test_update_task_exceptions(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: MockConfigEntry,
|
||||||
|
habitica: AsyncMock,
|
||||||
|
exception: Exception,
|
||||||
|
expected_exception: Exception,
|
||||||
|
exception_msg: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test Habitica task action exceptions."""
|
||||||
|
task_id = "5e2ea1df-f6e6-4ba3-bccb-97c5ec63e99b"
|
||||||
|
|
||||||
|
habitica.update_task.side_effect = exception
|
||||||
|
with pytest.raises(expected_exception, match=exception_msg):
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_UPDATE_REWARD,
|
||||||
|
service_data={
|
||||||
|
ATTR_CONFIG_ENTRY: config_entry.entry_id,
|
||||||
|
ATTR_TASK: task_id,
|
||||||
|
},
|
||||||
|
return_response=True,
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("habitica")
|
||||||
|
async def test_task_not_found(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: MockConfigEntry,
|
||||||
|
habitica: AsyncMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test Habitica task not found exceptions."""
|
||||||
|
task_id = "7f902bbc-eb3d-4a8f-82cf-4e2025d69af1"
|
||||||
|
|
||||||
|
with pytest.raises(
|
||||||
|
ServiceValidationError,
|
||||||
|
match="Unable to complete action, could not find the task '7f902bbc-eb3d-4a8f-82cf-4e2025d69af1'",
|
||||||
|
):
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_UPDATE_REWARD,
|
||||||
|
service_data={
|
||||||
|
ATTR_CONFIG_ENTRY: config_entry.entry_id,
|
||||||
|
ATTR_TASK: task_id,
|
||||||
|
},
|
||||||
|
return_response=True,
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("service_data", "call_args"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
{
|
||||||
|
ATTR_COST: 100,
|
||||||
|
},
|
||||||
|
Task(value=100),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
ATTR_RENAME: "RENAME",
|
||||||
|
},
|
||||||
|
Task(text="RENAME"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
ATTR_DESCRIPTION: "DESCRIPTION",
|
||||||
|
},
|
||||||
|
Task(notes="DESCRIPTION"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
ATTR_ALIAS: "ALIAS",
|
||||||
|
},
|
||||||
|
Task(alias="ALIAS"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_update_reward(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: MockConfigEntry,
|
||||||
|
habitica: AsyncMock,
|
||||||
|
service_data: dict[str, Any],
|
||||||
|
call_args: Task,
|
||||||
|
) -> None:
|
||||||
|
"""Test Habitica update_reward action."""
|
||||||
|
task_id = "5e2ea1df-f6e6-4ba3-bccb-97c5ec63e99b"
|
||||||
|
|
||||||
|
habitica.update_task.return_value = HabiticaTaskResponse.from_json(
|
||||||
|
load_fixture("task.json", DOMAIN)
|
||||||
|
)
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_UPDATE_REWARD,
|
||||||
|
service_data={
|
||||||
|
ATTR_CONFIG_ENTRY: config_entry.entry_id,
|
||||||
|
ATTR_TASK: task_id,
|
||||||
|
**service_data,
|
||||||
|
},
|
||||||
|
return_response=True,
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
habitica.update_task.assert_awaited_with(UUID(task_id), call_args)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_tags(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: MockConfigEntry,
|
||||||
|
habitica: AsyncMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test adding tags to a task."""
|
||||||
|
task_id = "5e2ea1df-f6e6-4ba3-bccb-97c5ec63e99b"
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_UPDATE_REWARD,
|
||||||
|
service_data={
|
||||||
|
ATTR_CONFIG_ENTRY: config_entry.entry_id,
|
||||||
|
ATTR_TASK: task_id,
|
||||||
|
ATTR_TAG: ["Schule"],
|
||||||
|
},
|
||||||
|
return_response=True,
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
call_args = habitica.update_task.call_args[0]
|
||||||
|
assert call_args[0] == UUID(task_id)
|
||||||
|
assert set(call_args[1]["tags"]) == {
|
||||||
|
UUID("2ac458af-0833-4f3f-bf04-98a0c33ef60b"),
|
||||||
|
UUID("3450351f-1323-4c7e-9fd2-0cdff25b3ce0"),
|
||||||
|
UUID("b2780f82-b3b5-49a3-a677-48f2c8c7e3bb"),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_create_new_tag(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: MockConfigEntry,
|
||||||
|
habitica: AsyncMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test adding a non-existent tag and create it as new."""
|
||||||
|
task_id = "5e2ea1df-f6e6-4ba3-bccb-97c5ec63e99b"
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_UPDATE_REWARD,
|
||||||
|
service_data={
|
||||||
|
ATTR_CONFIG_ENTRY: config_entry.entry_id,
|
||||||
|
ATTR_TASK: task_id,
|
||||||
|
ATTR_TAG: ["Home Assistant"],
|
||||||
|
},
|
||||||
|
return_response=True,
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
habitica.create_tag.assert_awaited_with("Home Assistant")
|
||||||
|
|
||||||
|
call_args = habitica.update_task.call_args[0]
|
||||||
|
assert call_args[0] == UUID(task_id)
|
||||||
|
assert set(call_args[1]["tags"]) == {
|
||||||
|
UUID("8bc0afbf-ab8e-49a4-982d-67a40557ed1a"),
|
||||||
|
UUID("3450351f-1323-4c7e-9fd2-0cdff25b3ce0"),
|
||||||
|
UUID("b2780f82-b3b5-49a3-a677-48f2c8c7e3bb"),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("exception", "expected_exception", "exception_msg"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
ERROR_TOO_MANY_REQUESTS,
|
||||||
|
HomeAssistantError,
|
||||||
|
RATE_LIMIT_EXCEPTION_MSG,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ERROR_BAD_REQUEST,
|
||||||
|
HomeAssistantError,
|
||||||
|
REQUEST_EXCEPTION_MSG,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
ClientError,
|
||||||
|
HomeAssistantError,
|
||||||
|
"Unable to connect to Habitica: ",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_create_new_tag_exception(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: MockConfigEntry,
|
||||||
|
habitica: AsyncMock,
|
||||||
|
exception: Exception,
|
||||||
|
expected_exception: Exception,
|
||||||
|
exception_msg: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test create new tag exception."""
|
||||||
|
task_id = "5e2ea1df-f6e6-4ba3-bccb-97c5ec63e99b"
|
||||||
|
|
||||||
|
habitica.create_tag.side_effect = exception
|
||||||
|
with pytest.raises(expected_exception, match=exception_msg):
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_UPDATE_REWARD,
|
||||||
|
service_data={
|
||||||
|
ATTR_CONFIG_ENTRY: config_entry.entry_id,
|
||||||
|
ATTR_TASK: task_id,
|
||||||
|
ATTR_TAG: ["Home Assistant"],
|
||||||
|
},
|
||||||
|
return_response=True,
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_remove_tags(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: MockConfigEntry,
|
||||||
|
habitica: AsyncMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test removing tags from a task."""
|
||||||
|
task_id = "5e2ea1df-f6e6-4ba3-bccb-97c5ec63e99b"
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_UPDATE_REWARD,
|
||||||
|
service_data={
|
||||||
|
ATTR_CONFIG_ENTRY: config_entry.entry_id,
|
||||||
|
ATTR_TASK: task_id,
|
||||||
|
ATTR_REMOVE_TAG: ["Kreativität"],
|
||||||
|
},
|
||||||
|
return_response=True,
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
call_args = habitica.update_task.call_args[0]
|
||||||
|
assert call_args[0] == UUID(task_id)
|
||||||
|
assert set(call_args[1]["tags"]) == {UUID("b2780f82-b3b5-49a3-a677-48f2c8c7e3bb")}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user