mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 21:27:38 +00:00
Add action for using transformation items to Habitica (#129606)
This commit is contained in:
parent
50cc6b4e01
commit
e26142949d
@ -26,6 +26,8 @@ ATTR_CONFIG_ENTRY = "config_entry"
|
|||||||
ATTR_SKILL = "skill"
|
ATTR_SKILL = "skill"
|
||||||
ATTR_TASK = "task"
|
ATTR_TASK = "task"
|
||||||
ATTR_DIRECTION = "direction"
|
ATTR_DIRECTION = "direction"
|
||||||
|
ATTR_TARGET = "target"
|
||||||
|
ATTR_ITEM = "item"
|
||||||
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"
|
||||||
@ -36,6 +38,9 @@ SERVICE_LEAVE_QUEST = "leave_quest"
|
|||||||
SERVICE_SCORE_HABIT = "score_habit"
|
SERVICE_SCORE_HABIT = "score_habit"
|
||||||
SERVICE_SCORE_REWARD = "score_reward"
|
SERVICE_SCORE_REWARD = "score_reward"
|
||||||
|
|
||||||
|
SERVICE_TRANSFORMATION = "transformation"
|
||||||
|
|
||||||
|
|
||||||
WARRIOR = "warrior"
|
WARRIOR = "warrior"
|
||||||
ROGUE = "rogue"
|
ROGUE = "rogue"
|
||||||
HEALER = "healer"
|
HEALER = "healer"
|
||||||
|
@ -187,6 +187,9 @@
|
|||||||
},
|
},
|
||||||
"score_reward": {
|
"score_reward": {
|
||||||
"service": "mdi:sack"
|
"service": "mdi:sack"
|
||||||
|
},
|
||||||
|
"transformation": {
|
||||||
|
"service": "mdi:flask-round-bottom"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,8 +27,10 @@ from .const import (
|
|||||||
ATTR_CONFIG_ENTRY,
|
ATTR_CONFIG_ENTRY,
|
||||||
ATTR_DATA,
|
ATTR_DATA,
|
||||||
ATTR_DIRECTION,
|
ATTR_DIRECTION,
|
||||||
|
ATTR_ITEM,
|
||||||
ATTR_PATH,
|
ATTR_PATH,
|
||||||
ATTR_SKILL,
|
ATTR_SKILL,
|
||||||
|
ATTR_TARGET,
|
||||||
ATTR_TASK,
|
ATTR_TASK,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
EVENT_API_CALL_SUCCESS,
|
EVENT_API_CALL_SUCCESS,
|
||||||
@ -42,6 +44,7 @@ from .const import (
|
|||||||
SERVICE_SCORE_HABIT,
|
SERVICE_SCORE_HABIT,
|
||||||
SERVICE_SCORE_REWARD,
|
SERVICE_SCORE_REWARD,
|
||||||
SERVICE_START_QUEST,
|
SERVICE_START_QUEST,
|
||||||
|
SERVICE_TRANSFORMATION,
|
||||||
)
|
)
|
||||||
from .types import HabiticaConfigEntry
|
from .types import HabiticaConfigEntry
|
||||||
|
|
||||||
@ -77,6 +80,14 @@ SERVICE_SCORE_TASK_SCHEMA = vol.Schema(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
SERVICE_TRANSFORMATION_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(ATTR_CONFIG_ENTRY): ConfigEntrySelector(),
|
||||||
|
vol.Required(ATTR_ITEM): cv.string,
|
||||||
|
vol.Required(ATTR_TARGET): cv.string,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_config_entry(hass: HomeAssistant, entry_id: str) -> HabiticaConfigEntry:
|
def get_config_entry(hass: HomeAssistant, entry_id: str) -> HabiticaConfigEntry:
|
||||||
"""Return config entry or raise if not found or not loaded."""
|
"""Return config entry or raise if not found or not loaded."""
|
||||||
@ -294,6 +305,83 @@ def async_setup_services(hass: HomeAssistant) -> None: # noqa: C901
|
|||||||
await coordinator.async_request_refresh()
|
await coordinator.async_request_refresh()
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
async def transformation(call: ServiceCall) -> ServiceResponse:
|
||||||
|
"""User a transformation item on a player character."""
|
||||||
|
|
||||||
|
entry = get_config_entry(hass, call.data[ATTR_CONFIG_ENTRY])
|
||||||
|
coordinator = entry.runtime_data
|
||||||
|
ITEMID_MAP = {
|
||||||
|
"snowball": {"itemId": "snowball"},
|
||||||
|
"spooky_sparkles": {"itemId": "spookySparkles"},
|
||||||
|
"seafoam": {"itemId": "seafoam"},
|
||||||
|
"shiny_seed": {"itemId": "shinySeed"},
|
||||||
|
}
|
||||||
|
# check if target is self
|
||||||
|
if call.data[ATTR_TARGET] in (
|
||||||
|
coordinator.data.user["id"],
|
||||||
|
coordinator.data.user["profile"]["name"],
|
||||||
|
coordinator.data.user["auth"]["local"]["username"],
|
||||||
|
):
|
||||||
|
target_id = coordinator.data.user["id"]
|
||||||
|
else:
|
||||||
|
# check if target is a party member
|
||||||
|
try:
|
||||||
|
party = await coordinator.api.groups.party.members.get()
|
||||||
|
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.NOT_FOUND:
|
||||||
|
raise ServiceValidationError(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="party_not_found",
|
||||||
|
) from e
|
||||||
|
raise HomeAssistantError(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="service_call_exception",
|
||||||
|
) from e
|
||||||
|
try:
|
||||||
|
target_id = next(
|
||||||
|
member["id"]
|
||||||
|
for member in party
|
||||||
|
if call.data[ATTR_TARGET].lower()
|
||||||
|
in (
|
||||||
|
member["id"],
|
||||||
|
member["auth"]["local"]["username"].lower(),
|
||||||
|
member["profile"]["name"].lower(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
except StopIteration as e:
|
||||||
|
raise ServiceValidationError(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="target_not_found",
|
||||||
|
translation_placeholders={"target": f"'{call.data[ATTR_TARGET]}'"},
|
||||||
|
) from e
|
||||||
|
try:
|
||||||
|
response: dict[str, Any] = await coordinator.api.user.class_.cast[
|
||||||
|
ITEMID_MAP[call.data[ATTR_ITEM]]["itemId"]
|
||||||
|
].post(targetId=target_id)
|
||||||
|
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:
|
||||||
|
raise ServiceValidationError(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="item_not_found",
|
||||||
|
translation_placeholders={"item": call.data[ATTR_ITEM]},
|
||||||
|
) from e
|
||||||
|
raise HomeAssistantError(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="service_call_exception",
|
||||||
|
) from e
|
||||||
|
else:
|
||||||
|
return response
|
||||||
|
|
||||||
hass.services.async_register(
|
hass.services.async_register(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
SERVICE_API_CALL,
|
SERVICE_API_CALL,
|
||||||
@ -323,3 +411,11 @@ def async_setup_services(hass: HomeAssistant) -> None: # noqa: C901
|
|||||||
schema=SERVICE_SCORE_TASK_SCHEMA,
|
schema=SERVICE_SCORE_TASK_SCHEMA,
|
||||||
supports_response=SupportsResponse.ONLY,
|
supports_response=SupportsResponse.ONLY,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
hass.services.async_register(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_TRANSFORMATION,
|
||||||
|
transformation,
|
||||||
|
schema=SERVICE_TRANSFORMATION_SCHEMA,
|
||||||
|
supports_response=SupportsResponse.ONLY,
|
||||||
|
)
|
||||||
|
@ -72,3 +72,25 @@ score_reward:
|
|||||||
fields:
|
fields:
|
||||||
config_entry: *config_entry
|
config_entry: *config_entry
|
||||||
task: *task
|
task: *task
|
||||||
|
transformation:
|
||||||
|
fields:
|
||||||
|
config_entry:
|
||||||
|
required: true
|
||||||
|
selector:
|
||||||
|
config_entry:
|
||||||
|
integration: habitica
|
||||||
|
item:
|
||||||
|
required: true
|
||||||
|
selector:
|
||||||
|
select:
|
||||||
|
options:
|
||||||
|
- "snowball"
|
||||||
|
- "spooky_sparkles"
|
||||||
|
- "seafoam"
|
||||||
|
- "shiny_seed"
|
||||||
|
mode: dropdown
|
||||||
|
translation_key: "transformation_item_select"
|
||||||
|
target:
|
||||||
|
required: true
|
||||||
|
selector:
|
||||||
|
text:
|
||||||
|
@ -321,6 +321,15 @@
|
|||||||
},
|
},
|
||||||
"quest_not_found": {
|
"quest_not_found": {
|
||||||
"message": "Unable to complete action, quest or group not found"
|
"message": "Unable to complete action, quest or group not found"
|
||||||
|
},
|
||||||
|
"target_not_found": {
|
||||||
|
"message": "Unable to find target {target} in your party"
|
||||||
|
},
|
||||||
|
"party_not_found": {
|
||||||
|
"message": "Unable to find target, you are currently not in a party. You can only target yourself"
|
||||||
|
},
|
||||||
|
"item_not_found": {
|
||||||
|
"message": "Unable to use {item}, you don't own this item."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"issues": {
|
"issues": {
|
||||||
@ -461,6 +470,24 @@
|
|||||||
"description": "The name (or task ID) of the custom reward."
|
"description": "The name (or task ID) of the custom reward."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"transformation": {
|
||||||
|
"name": "Use a transformation item",
|
||||||
|
"description": "Use a transformation item from your Habitica character's inventory on a member of your party or yourself.",
|
||||||
|
"fields": {
|
||||||
|
"config_entry": {
|
||||||
|
"name": "Select character",
|
||||||
|
"description": "Choose the Habitica character to use the transformation item."
|
||||||
|
},
|
||||||
|
"item": {
|
||||||
|
"name": "Transformation item",
|
||||||
|
"description": "Select the transformation item you want to use. Item must be in the characters inventory."
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"name": "Target character",
|
||||||
|
"description": "The name of the character you want to use the transformation item on. You can also specify the players username or user ID."
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"selector": {
|
"selector": {
|
||||||
@ -471,6 +498,14 @@
|
|||||||
"backstab": "Rogue: Backstab",
|
"backstab": "Rogue: Backstab",
|
||||||
"smash": "Warrior: Brutal smash"
|
"smash": "Warrior: Brutal smash"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"transformation_item_select": {
|
||||||
|
"options": {
|
||||||
|
"snowball": "Snowball",
|
||||||
|
"spooky_sparkles": "Spooky sparkles",
|
||||||
|
"seafoam": "Seafoam",
|
||||||
|
"shiny_seed": "Shiny seed"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
442
tests/components/habitica/fixtures/party_members.json
Normal file
442
tests/components/habitica/fixtures/party_members.json
Normal file
@ -0,0 +1,442 @@
|
|||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"_id": "a380546a-94be-4b8e-8a0b-23e0d5c03303",
|
||||||
|
"auth": {
|
||||||
|
"local": {
|
||||||
|
"username": "test-username"
|
||||||
|
},
|
||||||
|
"timestamps": {
|
||||||
|
"created": "2024-10-19T18:43:39.782Z",
|
||||||
|
"loggedin": "2024-10-31T16:13:35.048Z",
|
||||||
|
"updated": "2024-10-31T16:15:56.552Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"achievements": {
|
||||||
|
"ultimateGearSets": {
|
||||||
|
"healer": false,
|
||||||
|
"wizard": false,
|
||||||
|
"rogue": false,
|
||||||
|
"warrior": false
|
||||||
|
},
|
||||||
|
"streak": 0,
|
||||||
|
"challenges": [],
|
||||||
|
"perfect": 1,
|
||||||
|
"quests": {},
|
||||||
|
"purchasedEquipment": true,
|
||||||
|
"completedTask": true,
|
||||||
|
"partyUp": true
|
||||||
|
},
|
||||||
|
"backer": {},
|
||||||
|
"contributor": {},
|
||||||
|
"flags": {
|
||||||
|
"verifiedUsername": true,
|
||||||
|
"classSelected": true
|
||||||
|
},
|
||||||
|
"items": {
|
||||||
|
"gear": {
|
||||||
|
"owned": {
|
||||||
|
"headAccessory_special_blackHeadband": true,
|
||||||
|
"headAccessory_special_blueHeadband": true,
|
||||||
|
"headAccessory_special_greenHeadband": true,
|
||||||
|
"headAccessory_special_pinkHeadband": true,
|
||||||
|
"headAccessory_special_redHeadband": true,
|
||||||
|
"headAccessory_special_whiteHeadband": true,
|
||||||
|
"headAccessory_special_yellowHeadband": true,
|
||||||
|
"eyewear_special_blackTopFrame": true,
|
||||||
|
"eyewear_special_blueTopFrame": true,
|
||||||
|
"eyewear_special_greenTopFrame": true,
|
||||||
|
"eyewear_special_pinkTopFrame": true,
|
||||||
|
"eyewear_special_redTopFrame": true,
|
||||||
|
"eyewear_special_whiteTopFrame": true,
|
||||||
|
"eyewear_special_yellowTopFrame": true,
|
||||||
|
"eyewear_special_blackHalfMoon": true,
|
||||||
|
"eyewear_special_blueHalfMoon": true,
|
||||||
|
"eyewear_special_greenHalfMoon": true,
|
||||||
|
"eyewear_special_pinkHalfMoon": true,
|
||||||
|
"eyewear_special_redHalfMoon": true,
|
||||||
|
"eyewear_special_whiteHalfMoon": true,
|
||||||
|
"eyewear_special_yellowHalfMoon": true,
|
||||||
|
"armor_special_bardRobes": true,
|
||||||
|
"weapon_special_fall2024Warrior": true,
|
||||||
|
"shield_special_fall2024Warrior": true,
|
||||||
|
"head_special_fall2024Warrior": true,
|
||||||
|
"armor_special_fall2024Warrior": true,
|
||||||
|
"back_mystery_201402": true,
|
||||||
|
"body_mystery_202003": true,
|
||||||
|
"head_special_bardHat": true,
|
||||||
|
"weapon_wizard_0": true
|
||||||
|
},
|
||||||
|
"equipped": {
|
||||||
|
"weapon": "weapon_special_fall2024Warrior",
|
||||||
|
"armor": "armor_special_fall2024Warrior",
|
||||||
|
"head": "head_special_fall2024Warrior",
|
||||||
|
"shield": "shield_special_fall2024Warrior",
|
||||||
|
"back": "back_mystery_201402",
|
||||||
|
"headAccessory": "headAccessory_special_pinkHeadband",
|
||||||
|
"eyewear": "eyewear_special_pinkHalfMoon",
|
||||||
|
"body": "body_mystery_202003"
|
||||||
|
},
|
||||||
|
"costume": {
|
||||||
|
"armor": "armor_base_0",
|
||||||
|
"head": "head_base_0",
|
||||||
|
"shield": "shield_base_0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"special": {
|
||||||
|
"snowball": 99,
|
||||||
|
"spookySparkles": 99,
|
||||||
|
"shinySeed": 99,
|
||||||
|
"seafoam": 99,
|
||||||
|
"valentine": 0,
|
||||||
|
"valentineReceived": [],
|
||||||
|
"nye": 0,
|
||||||
|
"nyeReceived": [],
|
||||||
|
"greeting": 0,
|
||||||
|
"greetingReceived": [],
|
||||||
|
"thankyou": 0,
|
||||||
|
"thankyouReceived": [],
|
||||||
|
"birthday": 0,
|
||||||
|
"birthdayReceived": [],
|
||||||
|
"congrats": 0,
|
||||||
|
"congratsReceived": [],
|
||||||
|
"getwell": 0,
|
||||||
|
"getwellReceived": [],
|
||||||
|
"goodluck": 0,
|
||||||
|
"goodluckReceived": []
|
||||||
|
},
|
||||||
|
"pets": {
|
||||||
|
"Rat-Shade": 1,
|
||||||
|
"Gryphatrice-Jubilant": 1
|
||||||
|
},
|
||||||
|
"currentPet": "Gryphatrice-Jubilant",
|
||||||
|
"eggs": {
|
||||||
|
"Cactus": 1,
|
||||||
|
"Fox": 2,
|
||||||
|
"Wolf": 1
|
||||||
|
},
|
||||||
|
"hatchingPotions": {
|
||||||
|
"CottonCandyBlue": 1,
|
||||||
|
"RoyalPurple": 1
|
||||||
|
},
|
||||||
|
"food": {
|
||||||
|
"Meat": 2,
|
||||||
|
"Chocolate": 1,
|
||||||
|
"CottonCandyPink": 1,
|
||||||
|
"Candy_Zombie": 1
|
||||||
|
},
|
||||||
|
"mounts": {
|
||||||
|
"Velociraptor-Base": true,
|
||||||
|
"Gryphon-Gryphatrice": true
|
||||||
|
},
|
||||||
|
"currentMount": "Gryphon-Gryphatrice",
|
||||||
|
"quests": {
|
||||||
|
"dustbunnies": 1,
|
||||||
|
"vice1": 1,
|
||||||
|
"atom1": 1,
|
||||||
|
"moonstone1": 1,
|
||||||
|
"goldenknight1": 1,
|
||||||
|
"basilist": 1
|
||||||
|
},
|
||||||
|
"lastDrop": {
|
||||||
|
"date": "2024-10-31T16:13:34.952Z",
|
||||||
|
"count": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"party": {
|
||||||
|
"quest": {
|
||||||
|
"progress": {
|
||||||
|
"up": 0,
|
||||||
|
"down": 0,
|
||||||
|
"collectedItems": 0,
|
||||||
|
"collect": {}
|
||||||
|
},
|
||||||
|
"RSVPNeeded": false,
|
||||||
|
"key": "dustbunnies"
|
||||||
|
},
|
||||||
|
"order": "level",
|
||||||
|
"orderAscending": "ascending",
|
||||||
|
"_id": "94cd398c-2240-4320-956e-6d345cf2c0de"
|
||||||
|
},
|
||||||
|
"preferences": {
|
||||||
|
"size": "slim",
|
||||||
|
"hair": {
|
||||||
|
"color": "red",
|
||||||
|
"base": 3,
|
||||||
|
"bangs": 1,
|
||||||
|
"beard": 0,
|
||||||
|
"mustache": 0,
|
||||||
|
"flower": 1
|
||||||
|
},
|
||||||
|
"skin": "915533",
|
||||||
|
"shirt": "blue",
|
||||||
|
"chair": "handleless_pink",
|
||||||
|
"costume": false,
|
||||||
|
"sleep": false,
|
||||||
|
"disableClasses": false,
|
||||||
|
"tasks": {
|
||||||
|
"groupByChallenge": false,
|
||||||
|
"confirmScoreNotes": false,
|
||||||
|
"mirrorGroupTasks": [],
|
||||||
|
"activeFilter": {
|
||||||
|
"habit": "all",
|
||||||
|
"daily": "all",
|
||||||
|
"todo": "remaining",
|
||||||
|
"reward": "all"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"background": "violet"
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"name": "test-user"
|
||||||
|
},
|
||||||
|
"stats": {
|
||||||
|
"hp": 50,
|
||||||
|
"mp": 150.8,
|
||||||
|
"exp": 127,
|
||||||
|
"gp": 19.08650199252128,
|
||||||
|
"lvl": 99,
|
||||||
|
"class": "wizard",
|
||||||
|
"points": 0,
|
||||||
|
"str": 0,
|
||||||
|
"con": 0,
|
||||||
|
"int": 0,
|
||||||
|
"per": 0,
|
||||||
|
"buffs": {
|
||||||
|
"str": 50,
|
||||||
|
"int": 50,
|
||||||
|
"per": 50,
|
||||||
|
"con": 50,
|
||||||
|
"stealth": 0,
|
||||||
|
"streaks": false,
|
||||||
|
"seafoam": false,
|
||||||
|
"shinySeed": false,
|
||||||
|
"snowball": false,
|
||||||
|
"spookySparkles": false
|
||||||
|
},
|
||||||
|
"training": {
|
||||||
|
"int": 0,
|
||||||
|
"per": 0,
|
||||||
|
"str": 0,
|
||||||
|
"con": 0
|
||||||
|
},
|
||||||
|
"toNextLevel": 3580,
|
||||||
|
"maxHealth": 50,
|
||||||
|
"maxMP": 228
|
||||||
|
},
|
||||||
|
"inbox": {
|
||||||
|
"optOut": false
|
||||||
|
},
|
||||||
|
"loginIncentives": 6,
|
||||||
|
"id": "a380546a-94be-4b8e-8a0b-23e0d5c03303"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"_id": "ffce870c-3ff3-4fa4-bad1-87612e52b8e7",
|
||||||
|
"auth": {
|
||||||
|
"local": {
|
||||||
|
"username": "test-partymember-username"
|
||||||
|
},
|
||||||
|
"timestamps": {
|
||||||
|
"created": "2024-10-10T15:57:01.106Z",
|
||||||
|
"loggedin": "2024-10-30T19:37:01.970Z",
|
||||||
|
"updated": "2024-10-30T19:38:25.968Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"achievements": {
|
||||||
|
"ultimateGearSets": {
|
||||||
|
"healer": false,
|
||||||
|
"wizard": false,
|
||||||
|
"rogue": false,
|
||||||
|
"warrior": false
|
||||||
|
},
|
||||||
|
"streak": 0,
|
||||||
|
"challenges": [],
|
||||||
|
"perfect": 1,
|
||||||
|
"quests": {},
|
||||||
|
"completedTask": true,
|
||||||
|
"partyUp": true,
|
||||||
|
"snowball": 1,
|
||||||
|
"spookySparkles": 1,
|
||||||
|
"seafoam": 1,
|
||||||
|
"shinySeed": 1
|
||||||
|
},
|
||||||
|
"backer": {},
|
||||||
|
"contributor": {},
|
||||||
|
"flags": {
|
||||||
|
"verifiedUsername": true,
|
||||||
|
"classSelected": false
|
||||||
|
},
|
||||||
|
"items": {
|
||||||
|
"gear": {
|
||||||
|
"equipped": {
|
||||||
|
"armor": "armor_base_0",
|
||||||
|
"head": "head_base_0",
|
||||||
|
"shield": "shield_base_0"
|
||||||
|
},
|
||||||
|
"costume": {
|
||||||
|
"armor": "armor_base_0",
|
||||||
|
"head": "head_base_0",
|
||||||
|
"shield": "shield_base_0"
|
||||||
|
},
|
||||||
|
"owned": {
|
||||||
|
"headAccessory_special_blackHeadband": true,
|
||||||
|
"headAccessory_special_blueHeadband": true,
|
||||||
|
"headAccessory_special_greenHeadband": true,
|
||||||
|
"headAccessory_special_pinkHeadband": true,
|
||||||
|
"headAccessory_special_redHeadband": true,
|
||||||
|
"headAccessory_special_whiteHeadband": true,
|
||||||
|
"headAccessory_special_yellowHeadband": true,
|
||||||
|
"eyewear_special_blackTopFrame": true,
|
||||||
|
"eyewear_special_blueTopFrame": true,
|
||||||
|
"eyewear_special_greenTopFrame": true,
|
||||||
|
"eyewear_special_pinkTopFrame": true,
|
||||||
|
"eyewear_special_redTopFrame": true,
|
||||||
|
"eyewear_special_whiteTopFrame": true,
|
||||||
|
"eyewear_special_yellowTopFrame": true,
|
||||||
|
"eyewear_special_blackHalfMoon": true,
|
||||||
|
"eyewear_special_blueHalfMoon": true,
|
||||||
|
"eyewear_special_greenHalfMoon": true,
|
||||||
|
"eyewear_special_pinkHalfMoon": true,
|
||||||
|
"eyewear_special_redHalfMoon": true,
|
||||||
|
"eyewear_special_whiteHalfMoon": true,
|
||||||
|
"eyewear_special_yellowHalfMoon": true,
|
||||||
|
"armor_special_bardRobes": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"special": {
|
||||||
|
"snowball": 0,
|
||||||
|
"spookySparkles": 0,
|
||||||
|
"shinySeed": 0,
|
||||||
|
"seafoam": 0,
|
||||||
|
"valentine": 0,
|
||||||
|
"valentineReceived": [],
|
||||||
|
"nye": 0,
|
||||||
|
"nyeReceived": [],
|
||||||
|
"greeting": 0,
|
||||||
|
"greetingReceived": [],
|
||||||
|
"thankyou": 0,
|
||||||
|
"thankyouReceived": [],
|
||||||
|
"birthday": 0,
|
||||||
|
"birthdayReceived": [],
|
||||||
|
"congrats": 0,
|
||||||
|
"congratsReceived": [],
|
||||||
|
"getwell": 0,
|
||||||
|
"getwellReceived": [],
|
||||||
|
"goodluck": 0,
|
||||||
|
"goodluckReceived": []
|
||||||
|
},
|
||||||
|
"lastDrop": {
|
||||||
|
"count": 0,
|
||||||
|
"date": "2024-10-30T19:37:01.838Z"
|
||||||
|
},
|
||||||
|
"currentPet": "",
|
||||||
|
"currentMount": "",
|
||||||
|
"pets": {},
|
||||||
|
"eggs": {
|
||||||
|
"BearCub": 1,
|
||||||
|
"Cactus": 1
|
||||||
|
},
|
||||||
|
"hatchingPotions": {
|
||||||
|
"Skeleton": 1
|
||||||
|
},
|
||||||
|
"food": {
|
||||||
|
"Candy_Red": 1
|
||||||
|
},
|
||||||
|
"mounts": {},
|
||||||
|
"quests": {
|
||||||
|
"dustbunnies": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"party": {
|
||||||
|
"quest": {
|
||||||
|
"progress": {
|
||||||
|
"up": 0,
|
||||||
|
"down": 0,
|
||||||
|
"collectedItems": 0,
|
||||||
|
"collect": {}
|
||||||
|
},
|
||||||
|
"RSVPNeeded": true,
|
||||||
|
"key": "dustbunnies"
|
||||||
|
},
|
||||||
|
"order": "level",
|
||||||
|
"orderAscending": "ascending",
|
||||||
|
"_id": "94cd398c-2240-4320-956e-6d345cf2c0de"
|
||||||
|
},
|
||||||
|
"preferences": {
|
||||||
|
"size": "slim",
|
||||||
|
"hair": {
|
||||||
|
"color": "red",
|
||||||
|
"base": 3,
|
||||||
|
"bangs": 1,
|
||||||
|
"beard": 0,
|
||||||
|
"mustache": 0,
|
||||||
|
"flower": 1
|
||||||
|
},
|
||||||
|
"skin": "915533",
|
||||||
|
"shirt": "blue",
|
||||||
|
"chair": "none",
|
||||||
|
"costume": false,
|
||||||
|
"sleep": false,
|
||||||
|
"disableClasses": false,
|
||||||
|
"tasks": {
|
||||||
|
"groupByChallenge": false,
|
||||||
|
"confirmScoreNotes": false,
|
||||||
|
"mirrorGroupTasks": [],
|
||||||
|
"activeFilter": {
|
||||||
|
"habit": "all",
|
||||||
|
"daily": "all",
|
||||||
|
"todo": "remaining",
|
||||||
|
"reward": "all"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"background": "violet"
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"name": "test-partymember-displayname"
|
||||||
|
},
|
||||||
|
"stats": {
|
||||||
|
"buffs": {
|
||||||
|
"str": 1,
|
||||||
|
"int": 1,
|
||||||
|
"per": 1,
|
||||||
|
"con": 1,
|
||||||
|
"stealth": 0,
|
||||||
|
"streaks": false,
|
||||||
|
"seafoam": false,
|
||||||
|
"shinySeed": true,
|
||||||
|
"snowball": false,
|
||||||
|
"spookySparkles": false
|
||||||
|
},
|
||||||
|
"training": {
|
||||||
|
"int": 0,
|
||||||
|
"per": 0,
|
||||||
|
"str": 0,
|
||||||
|
"con": 0
|
||||||
|
},
|
||||||
|
"hp": 50,
|
||||||
|
"mp": 24,
|
||||||
|
"exp": 24,
|
||||||
|
"gp": 4,
|
||||||
|
"lvl": 1,
|
||||||
|
"class": "warrior",
|
||||||
|
"points": 0,
|
||||||
|
"str": 0,
|
||||||
|
"con": 0,
|
||||||
|
"int": 0,
|
||||||
|
"per": 0,
|
||||||
|
"toNextLevel": 25,
|
||||||
|
"maxHealth": 50,
|
||||||
|
"maxMP": 32
|
||||||
|
},
|
||||||
|
"inbox": {
|
||||||
|
"optOut": false
|
||||||
|
},
|
||||||
|
"loginIncentives": 1,
|
||||||
|
"id": "ffce870c-3ff3-4fa4-bad1-87612e52b8e7"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"notifications": [],
|
||||||
|
"userV": 96,
|
||||||
|
"appVersion": "5.29.0"
|
||||||
|
}
|
@ -2,6 +2,7 @@
|
|||||||
"data": {
|
"data": {
|
||||||
"api_user": "test-api-user",
|
"api_user": "test-api-user",
|
||||||
"profile": { "name": "test-user" },
|
"profile": { "name": "test-user" },
|
||||||
|
"auth": { "local": { "username": "test-username" } },
|
||||||
"stats": {
|
"stats": {
|
||||||
"buffs": {
|
"buffs": {
|
||||||
"str": 26,
|
"str": 26,
|
||||||
@ -65,6 +66,7 @@
|
|||||||
},
|
},
|
||||||
"needsCron": true,
|
"needsCron": true,
|
||||||
"lastCron": "2024-09-21T22:01:55.586Z",
|
"lastCron": "2024-09-21T22:01:55.586Z",
|
||||||
|
"id": "a380546a-94be-4b8e-8a0b-23e0d5c03303",
|
||||||
"items": {
|
"items": {
|
||||||
"gear": {
|
"gear": {
|
||||||
"equipped": {
|
"equipped": {
|
||||||
|
@ -10,7 +10,9 @@ import pytest
|
|||||||
from homeassistant.components.habitica.const import (
|
from homeassistant.components.habitica.const import (
|
||||||
ATTR_CONFIG_ENTRY,
|
ATTR_CONFIG_ENTRY,
|
||||||
ATTR_DIRECTION,
|
ATTR_DIRECTION,
|
||||||
|
ATTR_ITEM,
|
||||||
ATTR_SKILL,
|
ATTR_SKILL,
|
||||||
|
ATTR_TARGET,
|
||||||
ATTR_TASK,
|
ATTR_TASK,
|
||||||
DEFAULT_URL,
|
DEFAULT_URL,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
@ -23,12 +25,13 @@ from homeassistant.components.habitica.const import (
|
|||||||
SERVICE_SCORE_HABIT,
|
SERVICE_SCORE_HABIT,
|
||||||
SERVICE_SCORE_REWARD,
|
SERVICE_SCORE_REWARD,
|
||||||
SERVICE_START_QUEST,
|
SERVICE_START_QUEST,
|
||||||
|
SERVICE_TRANSFORMATION,
|
||||||
)
|
)
|
||||||
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
|
||||||
|
|
||||||
from .conftest import mock_called_with
|
from .conftest import load_json_object_fixture, mock_called_with
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
from tests.test_util.aiohttp import AiohttpClientMocker
|
from tests.test_util.aiohttp import AiohttpClientMocker
|
||||||
@ -62,6 +65,15 @@ async def load_entry(
|
|||||||
assert config_entry.state is ConfigEntryState.LOADED
|
assert config_entry.state is ConfigEntryState.LOADED
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def uuid_mock() -> Generator[None]:
|
||||||
|
"""Mock the UUID."""
|
||||||
|
with patch(
|
||||||
|
"uuid.uuid4", return_value="5d1935ff-80c8-443c-b2e9-733c66b44745"
|
||||||
|
) as uuid_mock:
|
||||||
|
yield uuid_mock.return_value
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("service_data", "item", "target_id"),
|
("service_data", "item", "target_id"),
|
||||||
[
|
[
|
||||||
@ -546,3 +558,234 @@ async def test_score_task_exceptions(
|
|||||||
return_response=True,
|
return_response=True,
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("service_data", "item", "target_id"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
{
|
||||||
|
ATTR_TARGET: "a380546a-94be-4b8e-8a0b-23e0d5c03303",
|
||||||
|
ATTR_ITEM: "spooky_sparkles",
|
||||||
|
},
|
||||||
|
"spookySparkles",
|
||||||
|
"a380546a-94be-4b8e-8a0b-23e0d5c03303",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
ATTR_TARGET: "a380546a-94be-4b8e-8a0b-23e0d5c03303",
|
||||||
|
ATTR_ITEM: "shiny_seed",
|
||||||
|
},
|
||||||
|
"shinySeed",
|
||||||
|
"a380546a-94be-4b8e-8a0b-23e0d5c03303",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
ATTR_TARGET: "a380546a-94be-4b8e-8a0b-23e0d5c03303",
|
||||||
|
ATTR_ITEM: "seafoam",
|
||||||
|
},
|
||||||
|
"seafoam",
|
||||||
|
"a380546a-94be-4b8e-8a0b-23e0d5c03303",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
ATTR_TARGET: "a380546a-94be-4b8e-8a0b-23e0d5c03303",
|
||||||
|
ATTR_ITEM: "snowball",
|
||||||
|
},
|
||||||
|
"snowball",
|
||||||
|
"a380546a-94be-4b8e-8a0b-23e0d5c03303",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
ATTR_TARGET: "test-user",
|
||||||
|
ATTR_ITEM: "spooky_sparkles",
|
||||||
|
},
|
||||||
|
"spookySparkles",
|
||||||
|
"a380546a-94be-4b8e-8a0b-23e0d5c03303",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
ATTR_TARGET: "test-username",
|
||||||
|
ATTR_ITEM: "spooky_sparkles",
|
||||||
|
},
|
||||||
|
"spookySparkles",
|
||||||
|
"a380546a-94be-4b8e-8a0b-23e0d5c03303",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
ATTR_TARGET: "ffce870c-3ff3-4fa4-bad1-87612e52b8e7",
|
||||||
|
ATTR_ITEM: "spooky_sparkles",
|
||||||
|
},
|
||||||
|
"spookySparkles",
|
||||||
|
"ffce870c-3ff3-4fa4-bad1-87612e52b8e7",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
ATTR_TARGET: "test-partymember-username",
|
||||||
|
ATTR_ITEM: "spooky_sparkles",
|
||||||
|
},
|
||||||
|
"spookySparkles",
|
||||||
|
"ffce870c-3ff3-4fa4-bad1-87612e52b8e7",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
ATTR_TARGET: "test-partymember-displayname",
|
||||||
|
ATTR_ITEM: "spooky_sparkles",
|
||||||
|
},
|
||||||
|
"spookySparkles",
|
||||||
|
"ffce870c-3ff3-4fa4-bad1-87612e52b8e7",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
ids=[],
|
||||||
|
)
|
||||||
|
async def test_transformation(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: MockConfigEntry,
|
||||||
|
mock_habitica: AiohttpClientMocker,
|
||||||
|
service_data: dict[str, Any],
|
||||||
|
item: str,
|
||||||
|
target_id: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test Habitica user transformation item action."""
|
||||||
|
mock_habitica.get(
|
||||||
|
f"{DEFAULT_URL}/api/v3/groups/party/members",
|
||||||
|
json=load_json_object_fixture("party_members.json", DOMAIN),
|
||||||
|
)
|
||||||
|
mock_habitica.post(
|
||||||
|
f"{DEFAULT_URL}/api/v3/user/class/cast/{item}?targetId={target_id}",
|
||||||
|
json={"success": True, "data": {}},
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_TRANSFORMATION,
|
||||||
|
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/user/class/cast/{item}?targetId={target_id}",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
(
|
||||||
|
"service_data",
|
||||||
|
"http_status_members",
|
||||||
|
"http_status_cast",
|
||||||
|
"expected_exception",
|
||||||
|
"expected_exception_msg",
|
||||||
|
),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
{
|
||||||
|
ATTR_TARGET: "user-not-found",
|
||||||
|
ATTR_ITEM: "spooky_sparkles",
|
||||||
|
},
|
||||||
|
HTTPStatus.OK,
|
||||||
|
HTTPStatus.OK,
|
||||||
|
ServiceValidationError,
|
||||||
|
"Unable to find target 'user-not-found' in your party",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
ATTR_TARGET: "test-partymember-username",
|
||||||
|
ATTR_ITEM: "spooky_sparkles",
|
||||||
|
},
|
||||||
|
HTTPStatus.TOO_MANY_REQUESTS,
|
||||||
|
HTTPStatus.OK,
|
||||||
|
ServiceValidationError,
|
||||||
|
RATE_LIMIT_EXCEPTION_MSG,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
ATTR_TARGET: "test-partymember-username",
|
||||||
|
ATTR_ITEM: "spooky_sparkles",
|
||||||
|
},
|
||||||
|
HTTPStatus.NOT_FOUND,
|
||||||
|
HTTPStatus.OK,
|
||||||
|
ServiceValidationError,
|
||||||
|
"Unable to find target, you are currently not in a party. You can only target yourself",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
ATTR_TARGET: "test-partymember-username",
|
||||||
|
ATTR_ITEM: "spooky_sparkles",
|
||||||
|
},
|
||||||
|
HTTPStatus.BAD_REQUEST,
|
||||||
|
HTTPStatus.OK,
|
||||||
|
HomeAssistantError,
|
||||||
|
"Unable to connect to Habitica, try again later",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
ATTR_TARGET: "test-partymember-username",
|
||||||
|
ATTR_ITEM: "spooky_sparkles",
|
||||||
|
},
|
||||||
|
HTTPStatus.OK,
|
||||||
|
HTTPStatus.TOO_MANY_REQUESTS,
|
||||||
|
ServiceValidationError,
|
||||||
|
RATE_LIMIT_EXCEPTION_MSG,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
ATTR_TARGET: "test-partymember-username",
|
||||||
|
ATTR_ITEM: "spooky_sparkles",
|
||||||
|
},
|
||||||
|
HTTPStatus.OK,
|
||||||
|
HTTPStatus.UNAUTHORIZED,
|
||||||
|
ServiceValidationError,
|
||||||
|
"Unable to use spooky_sparkles, you don't own this item",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
ATTR_TARGET: "test-partymember-username",
|
||||||
|
ATTR_ITEM: "spooky_sparkles",
|
||||||
|
},
|
||||||
|
HTTPStatus.OK,
|
||||||
|
HTTPStatus.BAD_REQUEST,
|
||||||
|
HomeAssistantError,
|
||||||
|
"Unable to connect to Habitica, try again later",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
@pytest.mark.usefixtures("mock_habitica")
|
||||||
|
async def test_transformation_exceptions(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: MockConfigEntry,
|
||||||
|
mock_habitica: AiohttpClientMocker,
|
||||||
|
service_data: dict[str, Any],
|
||||||
|
http_status_members: HTTPStatus,
|
||||||
|
http_status_cast: HTTPStatus,
|
||||||
|
expected_exception: Exception,
|
||||||
|
expected_exception_msg: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test Habitica transformation action exceptions."""
|
||||||
|
mock_habitica.get(
|
||||||
|
f"{DEFAULT_URL}/api/v3/groups/party/members",
|
||||||
|
json=load_json_object_fixture("party_members.json", DOMAIN),
|
||||||
|
status=http_status_members,
|
||||||
|
)
|
||||||
|
mock_habitica.post(
|
||||||
|
f"{DEFAULT_URL}/api/v3/user/class/cast/spookySparkles?targetId=ffce870c-3ff3-4fa4-bad1-87612e52b8e7",
|
||||||
|
json={"success": True, "data": {}},
|
||||||
|
status=http_status_cast,
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(expected_exception, match=expected_exception_msg):
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_TRANSFORMATION,
|
||||||
|
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