mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 16:57:53 +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_TASK = "task"
|
||||
ATTR_DIRECTION = "direction"
|
||||
ATTR_TARGET = "target"
|
||||
ATTR_ITEM = "item"
|
||||
SERVICE_CAST_SKILL = "cast_skill"
|
||||
SERVICE_START_QUEST = "start_quest"
|
||||
SERVICE_ACCEPT_QUEST = "accept_quest"
|
||||
@ -36,6 +38,9 @@ SERVICE_LEAVE_QUEST = "leave_quest"
|
||||
SERVICE_SCORE_HABIT = "score_habit"
|
||||
SERVICE_SCORE_REWARD = "score_reward"
|
||||
|
||||
SERVICE_TRANSFORMATION = "transformation"
|
||||
|
||||
|
||||
WARRIOR = "warrior"
|
||||
ROGUE = "rogue"
|
||||
HEALER = "healer"
|
||||
|
@ -187,6 +187,9 @@
|
||||
},
|
||||
"score_reward": {
|
||||
"service": "mdi:sack"
|
||||
},
|
||||
"transformation": {
|
||||
"service": "mdi:flask-round-bottom"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,8 +27,10 @@ from .const import (
|
||||
ATTR_CONFIG_ENTRY,
|
||||
ATTR_DATA,
|
||||
ATTR_DIRECTION,
|
||||
ATTR_ITEM,
|
||||
ATTR_PATH,
|
||||
ATTR_SKILL,
|
||||
ATTR_TARGET,
|
||||
ATTR_TASK,
|
||||
DOMAIN,
|
||||
EVENT_API_CALL_SUCCESS,
|
||||
@ -42,6 +44,7 @@ from .const import (
|
||||
SERVICE_SCORE_HABIT,
|
||||
SERVICE_SCORE_REWARD,
|
||||
SERVICE_START_QUEST,
|
||||
SERVICE_TRANSFORMATION,
|
||||
)
|
||||
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:
|
||||
"""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()
|
||||
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(
|
||||
DOMAIN,
|
||||
SERVICE_API_CALL,
|
||||
@ -323,3 +411,11 @@ def async_setup_services(hass: HomeAssistant) -> None: # noqa: C901
|
||||
schema=SERVICE_SCORE_TASK_SCHEMA,
|
||||
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:
|
||||
config_entry: *config_entry
|
||||
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": {
|
||||
"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": {
|
||||
@ -461,6 +470,24 @@
|
||||
"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": {
|
||||
@ -471,6 +498,14 @@
|
||||
"backstab": "Rogue: Backstab",
|
||||
"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": {
|
||||
"api_user": "test-api-user",
|
||||
"profile": { "name": "test-user" },
|
||||
"auth": { "local": { "username": "test-username" } },
|
||||
"stats": {
|
||||
"buffs": {
|
||||
"str": 26,
|
||||
@ -65,6 +66,7 @@
|
||||
},
|
||||
"needsCron": true,
|
||||
"lastCron": "2024-09-21T22:01:55.586Z",
|
||||
"id": "a380546a-94be-4b8e-8a0b-23e0d5c03303",
|
||||
"items": {
|
||||
"gear": {
|
||||
"equipped": {
|
||||
|
@ -10,7 +10,9 @@ import pytest
|
||||
from homeassistant.components.habitica.const import (
|
||||
ATTR_CONFIG_ENTRY,
|
||||
ATTR_DIRECTION,
|
||||
ATTR_ITEM,
|
||||
ATTR_SKILL,
|
||||
ATTR_TARGET,
|
||||
ATTR_TASK,
|
||||
DEFAULT_URL,
|
||||
DOMAIN,
|
||||
@ -23,12 +25,13 @@ from homeassistant.components.habitica.const import (
|
||||
SERVICE_SCORE_HABIT,
|
||||
SERVICE_SCORE_REWARD,
|
||||
SERVICE_START_QUEST,
|
||||
SERVICE_TRANSFORMATION,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.core import HomeAssistant
|
||||
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.test_util.aiohttp import AiohttpClientMocker
|
||||
@ -62,6 +65,15 @@ async def load_entry(
|
||||
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(
|
||||
("service_data", "item", "target_id"),
|
||||
[
|
||||
@ -546,3 +558,234 @@ async def test_score_task_exceptions(
|
||||
return_response=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