mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 11:17:21 +00:00
Add actions for scoring habits and rewards in Habitica (#129605)
This commit is contained in:
parent
d0ad834d93
commit
0677bba5bd
@ -25,6 +25,7 @@ UNIT_TASKS = "tasks"
|
|||||||
ATTR_CONFIG_ENTRY = "config_entry"
|
ATTR_CONFIG_ENTRY = "config_entry"
|
||||||
ATTR_SKILL = "skill"
|
ATTR_SKILL = "skill"
|
||||||
ATTR_TASK = "task"
|
ATTR_TASK = "task"
|
||||||
|
ATTR_DIRECTION = "direction"
|
||||||
SERVICE_CAST_SKILL = "cast_skill"
|
SERVICE_CAST_SKILL = "cast_skill"
|
||||||
SERVICE_START_QUEST = "start_quest"
|
SERVICE_START_QUEST = "start_quest"
|
||||||
SERVICE_ACCEPT_QUEST = "accept_quest"
|
SERVICE_ACCEPT_QUEST = "accept_quest"
|
||||||
@ -32,6 +33,9 @@ SERVICE_CANCEL_QUEST = "cancel_quest"
|
|||||||
SERVICE_ABORT_QUEST = "abort_quest"
|
SERVICE_ABORT_QUEST = "abort_quest"
|
||||||
SERVICE_REJECT_QUEST = "reject_quest"
|
SERVICE_REJECT_QUEST = "reject_quest"
|
||||||
SERVICE_LEAVE_QUEST = "leave_quest"
|
SERVICE_LEAVE_QUEST = "leave_quest"
|
||||||
|
SERVICE_SCORE_HABIT = "score_habit"
|
||||||
|
SERVICE_SCORE_REWARD = "score_reward"
|
||||||
|
|
||||||
WARRIOR = "warrior"
|
WARRIOR = "warrior"
|
||||||
ROGUE = "rogue"
|
ROGUE = "rogue"
|
||||||
HEALER = "healer"
|
HEALER = "healer"
|
||||||
|
@ -181,6 +181,12 @@
|
|||||||
},
|
},
|
||||||
"start_quest": {
|
"start_quest": {
|
||||||
"service": "mdi:script-text-key"
|
"service": "mdi:script-text-key"
|
||||||
|
},
|
||||||
|
"score_habit": {
|
||||||
|
"service": "mdi:counter"
|
||||||
|
},
|
||||||
|
"score_reward": {
|
||||||
|
"service": "mdi:sack"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ from .const import (
|
|||||||
ATTR_ARGS,
|
ATTR_ARGS,
|
||||||
ATTR_CONFIG_ENTRY,
|
ATTR_CONFIG_ENTRY,
|
||||||
ATTR_DATA,
|
ATTR_DATA,
|
||||||
|
ATTR_DIRECTION,
|
||||||
ATTR_PATH,
|
ATTR_PATH,
|
||||||
ATTR_SKILL,
|
ATTR_SKILL,
|
||||||
ATTR_TASK,
|
ATTR_TASK,
|
||||||
@ -37,6 +38,8 @@ from .const import (
|
|||||||
SERVICE_CAST_SKILL,
|
SERVICE_CAST_SKILL,
|
||||||
SERVICE_LEAVE_QUEST,
|
SERVICE_LEAVE_QUEST,
|
||||||
SERVICE_REJECT_QUEST,
|
SERVICE_REJECT_QUEST,
|
||||||
|
SERVICE_SCORE_HABIT,
|
||||||
|
SERVICE_SCORE_REWARD,
|
||||||
SERVICE_START_QUEST,
|
SERVICE_START_QUEST,
|
||||||
)
|
)
|
||||||
from .types import HabiticaConfigEntry
|
from .types import HabiticaConfigEntry
|
||||||
@ -65,6 +68,13 @@ SERVICE_MANAGE_QUEST_SCHEMA = vol.Schema(
|
|||||||
vol.Required(ATTR_CONFIG_ENTRY): ConfigEntrySelector(),
|
vol.Required(ATTR_CONFIG_ENTRY): ConfigEntrySelector(),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
SERVICE_SCORE_TASK_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(ATTR_CONFIG_ENTRY): ConfigEntrySelector(),
|
||||||
|
vol.Required(ATTR_TASK): cv.string,
|
||||||
|
vol.Optional(ATTR_DIRECTION): cv.string,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_config_entry(hass: HomeAssistant, entry_id: str) -> HabiticaConfigEntry:
|
def get_config_entry(hass: HomeAssistant, entry_id: str) -> HabiticaConfigEntry:
|
||||||
@ -82,7 +92,7 @@ def get_config_entry(hass: HomeAssistant, entry_id: str) -> HabiticaConfigEntry:
|
|||||||
return entry
|
return entry
|
||||||
|
|
||||||
|
|
||||||
def async_setup_services(hass: HomeAssistant) -> None:
|
def async_setup_services(hass: HomeAssistant) -> None: # noqa: C901
|
||||||
"""Set up services for Habitica integration."""
|
"""Set up services for Habitica integration."""
|
||||||
|
|
||||||
async def handle_api_call(call: ServiceCall) -> None:
|
async def handle_api_call(call: ServiceCall) -> None:
|
||||||
@ -223,6 +233,53 @@ def async_setup_services(hass: HomeAssistant) -> None:
|
|||||||
supports_response=SupportsResponse.ONLY,
|
supports_response=SupportsResponse.ONLY,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def score_task(call: ServiceCall) -> ServiceResponse:
|
||||||
|
"""Score a task action."""
|
||||||
|
entry = get_config_entry(hass, call.data[ATTR_CONFIG_ENTRY])
|
||||||
|
coordinator = entry.runtime_data
|
||||||
|
try:
|
||||||
|
task_id, task_value = next(
|
||||||
|
(task["id"], task.get("value"))
|
||||||
|
for task in coordinator.data.tasks
|
||||||
|
if call.data[ATTR_TASK] in (task["id"], task.get("alias"))
|
||||||
|
or call.data[ATTR_TASK] == task["text"]
|
||||||
|
)
|
||||||
|
except StopIteration as e:
|
||||||
|
raise ServiceValidationError(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="task_not_found",
|
||||||
|
translation_placeholders={"task": f"'{call.data[ATTR_TASK]}'"},
|
||||||
|
) from e
|
||||||
|
|
||||||
|
try:
|
||||||
|
response: dict[str, Any] = (
|
||||||
|
await coordinator.api.tasks[task_id]
|
||||||
|
.score[call.data.get(ATTR_DIRECTION, "up")]
|
||||||
|
.post()
|
||||||
|
)
|
||||||
|
except ClientResponseError as e:
|
||||||
|
if e.status == HTTPStatus.TOO_MANY_REQUESTS:
|
||||||
|
raise ServiceValidationError(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="setup_rate_limit_exception",
|
||||||
|
) from e
|
||||||
|
if e.status == HTTPStatus.UNAUTHORIZED and task_value is not None:
|
||||||
|
raise ServiceValidationError(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="not_enough_gold",
|
||||||
|
translation_placeholders={
|
||||||
|
"gold": f"{coordinator.data.user["stats"]["gp"]:.2f} GP",
|
||||||
|
"cost": f"{task_value} GP",
|
||||||
|
},
|
||||||
|
) from e
|
||||||
|
raise HomeAssistantError(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="service_call_exception",
|
||||||
|
) from e
|
||||||
|
else:
|
||||||
|
await coordinator.async_request_refresh()
|
||||||
|
return response
|
||||||
|
|
||||||
hass.services.async_register(
|
hass.services.async_register(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
SERVICE_API_CALL,
|
SERVICE_API_CALL,
|
||||||
@ -237,3 +294,18 @@ def async_setup_services(hass: HomeAssistant) -> None:
|
|||||||
schema=SERVICE_CAST_SKILL_SCHEMA,
|
schema=SERVICE_CAST_SKILL_SCHEMA,
|
||||||
supports_response=SupportsResponse.ONLY,
|
supports_response=SupportsResponse.ONLY,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
hass.services.async_register(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_SCORE_HABIT,
|
||||||
|
score_task,
|
||||||
|
schema=SERVICE_SCORE_TASK_SCHEMA,
|
||||||
|
supports_response=SupportsResponse.ONLY,
|
||||||
|
)
|
||||||
|
hass.services.async_register(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_SCORE_REWARD,
|
||||||
|
score_task,
|
||||||
|
schema=SERVICE_SCORE_TASK_SCHEMA,
|
||||||
|
supports_response=SupportsResponse.ONLY,
|
||||||
|
)
|
||||||
|
@ -33,7 +33,7 @@ cast_skill:
|
|||||||
- "fireball"
|
- "fireball"
|
||||||
mode: dropdown
|
mode: dropdown
|
||||||
translation_key: "skill_select"
|
translation_key: "skill_select"
|
||||||
task:
|
task: &task
|
||||||
required: true
|
required: true
|
||||||
selector:
|
selector:
|
||||||
text:
|
text:
|
||||||
@ -55,3 +55,20 @@ abort_quest:
|
|||||||
leave_quest:
|
leave_quest:
|
||||||
fields:
|
fields:
|
||||||
config_entry: *config_entry
|
config_entry: *config_entry
|
||||||
|
score_habit:
|
||||||
|
fields:
|
||||||
|
config_entry: *config_entry
|
||||||
|
task: *task
|
||||||
|
direction:
|
||||||
|
required: true
|
||||||
|
selector:
|
||||||
|
select:
|
||||||
|
options:
|
||||||
|
- value: up
|
||||||
|
label: "➕"
|
||||||
|
- value: down
|
||||||
|
label: "➖"
|
||||||
|
score_reward:
|
||||||
|
fields:
|
||||||
|
config_entry: *config_entry
|
||||||
|
task: *task
|
||||||
|
@ -301,6 +301,9 @@
|
|||||||
"not_enough_mana": {
|
"not_enough_mana": {
|
||||||
"message": "Unable to cast skill, not enough mana. Your character has {mana}, but the skill costs {cost}."
|
"message": "Unable to cast skill, not enough mana. Your character has {mana}, but the skill costs {cost}."
|
||||||
},
|
},
|
||||||
|
"not_enough_gold": {
|
||||||
|
"message": "Unable to buy reward, not enough gold. Your character has {gold}, but the reward costs {cost}."
|
||||||
|
},
|
||||||
"skill_not_found": {
|
"skill_not_found": {
|
||||||
"message": "Unable to cast skill, your character does not have the skill or spell {skill}."
|
"message": "Unable to cast skill, your character does not have the skill or spell {skill}."
|
||||||
},
|
},
|
||||||
@ -311,7 +314,7 @@
|
|||||||
"message": "The selected character is currently not loaded or disabled in Home Assistant."
|
"message": "The selected character is currently not loaded or disabled in Home Assistant."
|
||||||
},
|
},
|
||||||
"task_not_found": {
|
"task_not_found": {
|
||||||
"message": "Unable to cast skill, could not find the task {task}"
|
"message": "Unable to complete action, could not find the task {task}"
|
||||||
},
|
},
|
||||||
"quest_action_unallowed": {
|
"quest_action_unallowed": {
|
||||||
"message": "Action not allowed, only quest leader or group leader can perform this action"
|
"message": "Action not allowed, only quest leader or group leader can perform this action"
|
||||||
@ -350,7 +353,7 @@
|
|||||||
"description": "Use a skill or spell from your Habitica character on a specific task to affect its progress or status.",
|
"description": "Use a skill or spell from your Habitica character on a specific task to affect its progress or status.",
|
||||||
"fields": {
|
"fields": {
|
||||||
"config_entry": {
|
"config_entry": {
|
||||||
"name": "Select character",
|
"name": "[%key:component::habitica::common::config_entry_name%]",
|
||||||
"description": "Choose the Habitica character to cast the skill."
|
"description": "Choose the Habitica character to cast the skill."
|
||||||
},
|
},
|
||||||
"skill": {
|
"skill": {
|
||||||
@ -422,6 +425,38 @@
|
|||||||
"description": "[%key:component::habitica::services::accept_quest::fields::config_entry::description%]"
|
"description": "[%key:component::habitica::services::accept_quest::fields::config_entry::description%]"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"score_habit": {
|
||||||
|
"name": "Track a habit",
|
||||||
|
"description": "Increase the positive or negative streak of a habit to track its progress.",
|
||||||
|
"fields": {
|
||||||
|
"config_entry": {
|
||||||
|
"name": "[%key:component::habitica::common::config_entry_name%]",
|
||||||
|
"description": "Select the Habitica character tracking your habit."
|
||||||
|
},
|
||||||
|
"task": {
|
||||||
|
"name": "Habit name",
|
||||||
|
"description": "The name (or task ID) of the Habitica habit."
|
||||||
|
},
|
||||||
|
"direction": {
|
||||||
|
"name": "Reward or loss",
|
||||||
|
"description": "Is it positive or negative progress you want to track for your habit."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"score_reward": {
|
||||||
|
"name": "Buy a reward",
|
||||||
|
"description": "Reward yourself and buy one of your custom rewards with gold earned by fulfilling tasks.",
|
||||||
|
"fields": {
|
||||||
|
"config_entry": {
|
||||||
|
"name": "[%key:component::habitica::common::config_entry_name%]",
|
||||||
|
"description": "Select the Habitica character buying the reward."
|
||||||
|
},
|
||||||
|
"task": {
|
||||||
|
"name": "Reward name",
|
||||||
|
"description": "The name (or task ID) of the custom reward."
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"selector": {
|
"selector": {
|
||||||
|
@ -34,7 +34,7 @@ def mock_called_with(
|
|||||||
(
|
(
|
||||||
call
|
call
|
||||||
for call in mock_client.mock_calls
|
for call in mock_client.mock_calls
|
||||||
if call[0] == method.upper() and call[1] == URL(url)
|
if call[0].upper() == method.upper() and call[1] == URL(url)
|
||||||
),
|
),
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
|
@ -121,7 +121,8 @@
|
|||||||
"createdAt": "2024-07-07T17:51:53.264Z",
|
"createdAt": "2024-07-07T17:51:53.264Z",
|
||||||
"updatedAt": "2024-07-12T09:58:45.438Z",
|
"updatedAt": "2024-07-12T09:58:45.438Z",
|
||||||
"userId": "5f359083-ef78-4af0-985a-0b2c6d05797c",
|
"userId": "5f359083-ef78-4af0-985a-0b2c6d05797c",
|
||||||
"id": "e97659e0-2c42-4599-a7bb-00282adc410d"
|
"id": "e97659e0-2c42-4599-a7bb-00282adc410d",
|
||||||
|
"alias": "create_a_task"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"_id": "564b9ac9-c53d-4638-9e7f-1cd96fe19baa",
|
"_id": "564b9ac9-c53d-4638-9e7f-1cd96fe19baa",
|
||||||
|
@ -9,6 +9,7 @@ import pytest
|
|||||||
|
|
||||||
from homeassistant.components.habitica.const import (
|
from homeassistant.components.habitica.const import (
|
||||||
ATTR_CONFIG_ENTRY,
|
ATTR_CONFIG_ENTRY,
|
||||||
|
ATTR_DIRECTION,
|
||||||
ATTR_SKILL,
|
ATTR_SKILL,
|
||||||
ATTR_TASK,
|
ATTR_TASK,
|
||||||
DEFAULT_URL,
|
DEFAULT_URL,
|
||||||
@ -19,6 +20,8 @@ from homeassistant.components.habitica.const import (
|
|||||||
SERVICE_CAST_SKILL,
|
SERVICE_CAST_SKILL,
|
||||||
SERVICE_LEAVE_QUEST,
|
SERVICE_LEAVE_QUEST,
|
||||||
SERVICE_REJECT_QUEST,
|
SERVICE_REJECT_QUEST,
|
||||||
|
SERVICE_SCORE_HABIT,
|
||||||
|
SERVICE_SCORE_REWARD,
|
||||||
SERVICE_START_QUEST,
|
SERVICE_START_QUEST,
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntryState
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
@ -168,7 +171,7 @@ async def test_cast_skill(
|
|||||||
},
|
},
|
||||||
HTTPStatus.OK,
|
HTTPStatus.OK,
|
||||||
ServiceValidationError,
|
ServiceValidationError,
|
||||||
"Unable to cast skill, could not find the task 'task-not-found",
|
"Unable to complete action, could not find the task 'task-not-found'",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
@ -377,3 +380,169 @@ async def test_handle_quests_exceptions(
|
|||||||
return_response=True,
|
return_response=True,
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("service", "service_data", "task_id"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
SERVICE_SCORE_HABIT,
|
||||||
|
{
|
||||||
|
ATTR_TASK: "e97659e0-2c42-4599-a7bb-00282adc410d",
|
||||||
|
ATTR_DIRECTION: "up",
|
||||||
|
},
|
||||||
|
"e97659e0-2c42-4599-a7bb-00282adc410d",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
SERVICE_SCORE_HABIT,
|
||||||
|
{
|
||||||
|
ATTR_TASK: "e97659e0-2c42-4599-a7bb-00282adc410d",
|
||||||
|
ATTR_DIRECTION: "down",
|
||||||
|
},
|
||||||
|
"e97659e0-2c42-4599-a7bb-00282adc410d",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
SERVICE_SCORE_REWARD,
|
||||||
|
{
|
||||||
|
ATTR_TASK: "5e2ea1df-f6e6-4ba3-bccb-97c5ec63e99b",
|
||||||
|
},
|
||||||
|
"5e2ea1df-f6e6-4ba3-bccb-97c5ec63e99b",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
SERVICE_SCORE_HABIT,
|
||||||
|
{
|
||||||
|
ATTR_TASK: "Füge eine Aufgabe zu Habitica hinzu",
|
||||||
|
ATTR_DIRECTION: "up",
|
||||||
|
},
|
||||||
|
"e97659e0-2c42-4599-a7bb-00282adc410d",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
SERVICE_SCORE_HABIT,
|
||||||
|
{
|
||||||
|
ATTR_TASK: "create_a_task",
|
||||||
|
ATTR_DIRECTION: "up",
|
||||||
|
},
|
||||||
|
"e97659e0-2c42-4599-a7bb-00282adc410d",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
ids=[
|
||||||
|
"habit score up",
|
||||||
|
"habit score down",
|
||||||
|
"buy reward",
|
||||||
|
"match task by name",
|
||||||
|
"match task by alias",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_score_task(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: MockConfigEntry,
|
||||||
|
mock_habitica: AiohttpClientMocker,
|
||||||
|
service: str,
|
||||||
|
service_data: dict[str, Any],
|
||||||
|
task_id: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test Habitica score task action."""
|
||||||
|
|
||||||
|
mock_habitica.post(
|
||||||
|
f"{DEFAULT_URL}/api/v3/tasks/{task_id}/score/{service_data.get(ATTR_DIRECTION, "up")}",
|
||||||
|
json={"success": True, "data": {}},
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
service,
|
||||||
|
service_data={
|
||||||
|
ATTR_CONFIG_ENTRY: config_entry.entry_id,
|
||||||
|
**service_data,
|
||||||
|
},
|
||||||
|
return_response=True,
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert mock_called_with(
|
||||||
|
mock_habitica,
|
||||||
|
"post",
|
||||||
|
f"{DEFAULT_URL}/api/v3/tasks/{task_id}/score/{service_data.get(ATTR_DIRECTION, "up")}",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
(
|
||||||
|
"service_data",
|
||||||
|
"http_status",
|
||||||
|
"expected_exception",
|
||||||
|
"expected_exception_msg",
|
||||||
|
),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
{
|
||||||
|
ATTR_TASK: "task does not exist",
|
||||||
|
ATTR_DIRECTION: "up",
|
||||||
|
},
|
||||||
|
HTTPStatus.OK,
|
||||||
|
ServiceValidationError,
|
||||||
|
"Unable to complete action, could not find the task 'task does not exist'",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
ATTR_TASK: "e97659e0-2c42-4599-a7bb-00282adc410d",
|
||||||
|
ATTR_DIRECTION: "up",
|
||||||
|
},
|
||||||
|
HTTPStatus.TOO_MANY_REQUESTS,
|
||||||
|
ServiceValidationError,
|
||||||
|
RATE_LIMIT_EXCEPTION_MSG,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
ATTR_TASK: "e97659e0-2c42-4599-a7bb-00282adc410d",
|
||||||
|
ATTR_DIRECTION: "up",
|
||||||
|
},
|
||||||
|
HTTPStatus.BAD_REQUEST,
|
||||||
|
HomeAssistantError,
|
||||||
|
REQUEST_EXCEPTION_MSG,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
ATTR_TASK: "5e2ea1df-f6e6-4ba3-bccb-97c5ec63e99b",
|
||||||
|
ATTR_DIRECTION: "up",
|
||||||
|
},
|
||||||
|
HTTPStatus.UNAUTHORIZED,
|
||||||
|
HomeAssistantError,
|
||||||
|
"Unable to buy reward, not enough gold. Your character has 137.63 GP, but the reward costs 10 GP",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
@pytest.mark.usefixtures("mock_habitica")
|
||||||
|
async def test_score_task_exceptions(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: MockConfigEntry,
|
||||||
|
mock_habitica: AiohttpClientMocker,
|
||||||
|
service_data: dict[str, Any],
|
||||||
|
http_status: HTTPStatus,
|
||||||
|
expected_exception: Exception,
|
||||||
|
expected_exception_msg: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test Habitica score task action exceptions."""
|
||||||
|
|
||||||
|
mock_habitica.post(
|
||||||
|
f"{DEFAULT_URL}/api/v3/tasks/e97659e0-2c42-4599-a7bb-00282adc410d/score/up",
|
||||||
|
json={"success": True, "data": {}},
|
||||||
|
status=http_status,
|
||||||
|
)
|
||||||
|
mock_habitica.post(
|
||||||
|
f"{DEFAULT_URL}/api/v3/tasks/5e2ea1df-f6e6-4ba3-bccb-97c5ec63e99b/score/up",
|
||||||
|
json={"success": True, "data": {}},
|
||||||
|
status=http_status,
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(expected_exception, match=expected_exception_msg):
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_SCORE_HABIT,
|
||||||
|
service_data={
|
||||||
|
ATTR_CONFIG_ENTRY: config_entry.entry_id,
|
||||||
|
**service_data,
|
||||||
|
},
|
||||||
|
return_response=True,
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user