mirror of
https://github.com/home-assistant/core.git
synced 2025-09-08 06:11:31 +02:00
Fix endpoint deprecation warning in Mastodon (#151275)
This commit is contained in:
@@ -2,7 +2,14 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from mastodon.Mastodon import Account, Instance, InstanceV2, Mastodon, MastodonError
|
||||
from mastodon.Mastodon import (
|
||||
Account,
|
||||
Instance,
|
||||
InstanceV2,
|
||||
Mastodon,
|
||||
MastodonError,
|
||||
MastodonNotFoundError,
|
||||
)
|
||||
|
||||
from homeassistant.const import (
|
||||
CONF_ACCESS_TOKEN,
|
||||
@@ -105,7 +112,11 @@ def setup_mastodon(
|
||||
entry.data[CONF_ACCESS_TOKEN],
|
||||
)
|
||||
|
||||
instance = client.instance()
|
||||
try:
|
||||
instance = client.instance_v2()
|
||||
except MastodonNotFoundError:
|
||||
instance = client.instance_v1()
|
||||
|
||||
account = client.account_verify_credentials()
|
||||
|
||||
return client, instance, account
|
||||
|
@@ -7,7 +7,9 @@ from typing import Any
|
||||
from mastodon.Mastodon import (
|
||||
Account,
|
||||
Instance,
|
||||
InstanceV2,
|
||||
MastodonNetworkError,
|
||||
MastodonNotFoundError,
|
||||
MastodonUnauthorizedError,
|
||||
)
|
||||
import voluptuous as vol
|
||||
@@ -61,7 +63,7 @@ class MastodonConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
client_secret: str,
|
||||
access_token: str,
|
||||
) -> tuple[
|
||||
Instance | None,
|
||||
InstanceV2 | Instance | None,
|
||||
Account | None,
|
||||
dict[str, str],
|
||||
]:
|
||||
@@ -73,7 +75,10 @@ class MastodonConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
client_secret,
|
||||
access_token,
|
||||
)
|
||||
instance = client.instance()
|
||||
try:
|
||||
instance = client.instance_v2()
|
||||
except MastodonNotFoundError:
|
||||
instance = client.instance_v1()
|
||||
account = client.account_verify_credentials()
|
||||
|
||||
except MastodonNetworkError:
|
||||
|
@@ -4,7 +4,7 @@ from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from mastodon.Mastodon import Account, Instance
|
||||
from mastodon.Mastodon import Account, Instance, InstanceV2, MastodonNotFoundError
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
@@ -27,11 +27,16 @@ async def async_get_config_entry_diagnostics(
|
||||
}
|
||||
|
||||
|
||||
def get_diagnostics(config_entry: MastodonConfigEntry) -> tuple[Instance, Account]:
|
||||
def get_diagnostics(
|
||||
config_entry: MastodonConfigEntry,
|
||||
) -> tuple[InstanceV2 | Instance, Account]:
|
||||
"""Get mastodon diagnostics."""
|
||||
client = config_entry.runtime_data.client
|
||||
|
||||
instance = client.instance()
|
||||
try:
|
||||
instance = client.instance_v2()
|
||||
except MastodonNotFoundError:
|
||||
instance = client.instance_v1()
|
||||
account = client.account_verify_credentials()
|
||||
|
||||
return instance, account
|
||||
|
@@ -32,12 +32,16 @@ def mock_mastodon_client() -> Generator[AsyncMock]:
|
||||
) as mock_client,
|
||||
):
|
||||
client = mock_client.return_value
|
||||
client.instance.return_value = InstanceV2.from_json(
|
||||
client.instance_v1.return_value = InstanceV2.from_json(
|
||||
load_fixture("instance.json", DOMAIN)
|
||||
)
|
||||
client.instance_v2.return_value = InstanceV2.from_json(
|
||||
load_fixture("instance.json", DOMAIN)
|
||||
)
|
||||
client.account_verify_credentials.return_value = Account.from_json(
|
||||
load_fixture("account_verify_credentials.json", DOMAIN)
|
||||
)
|
||||
client.mastodon_api_version = 2
|
||||
client.status_post.return_value = None
|
||||
yield client
|
||||
|
||||
|
@@ -83,3 +83,87 @@
|
||||
}),
|
||||
})
|
||||
# ---
|
||||
# name: test_entry_diagnostics_fallback_to_instance_v1
|
||||
dict({
|
||||
'account': dict({
|
||||
'acct': 'trwnh',
|
||||
'avatar': 'https://files.mastodon.social/accounts/avatars/000/014/715/original/051c958388818705.png',
|
||||
'avatar_static': 'https://files.mastodon.social/accounts/avatars/000/014/715/original/051c958388818705.png',
|
||||
'bot': True,
|
||||
'created_at': '2016-11-24T00:00:00+00:00',
|
||||
'discoverable': True,
|
||||
'display_name': 'infinite love ⴳ',
|
||||
'emojis': list([
|
||||
]),
|
||||
'fields': list([
|
||||
dict({
|
||||
'name': 'Website',
|
||||
'value': '<a href="https://trwnh.com" target="_blank" rel="nofollow noopener me" translate="no"><span class="invisible">https://</span><span class="">trwnh.com</span><span class="invisible"></span></a>',
|
||||
'verified_at': '2019-08-29T04:14:55.571+00:00',
|
||||
}),
|
||||
dict({
|
||||
'name': 'Portfolio',
|
||||
'value': '<a href="https://abdullahtarawneh.com" target="_blank" rel="nofollow noopener me" translate="no"><span class="invisible">https://</span><span class="">abdullahtarawneh.com</span><span class="invisible"></span></a>',
|
||||
'verified_at': '2021-02-11T20:34:13.574+00:00',
|
||||
}),
|
||||
dict({
|
||||
'name': 'Fan of:',
|
||||
'value': 'Punk-rock and post-hardcore (Circa Survive, letlive., La Dispute, THE FEVER 333)Manga (Yu-Gi-Oh!, One Piece, JoJo's Bizarre Adventure, Death Note, Shaman King)Platformers and RPGs (Banjo-Kazooie, Boktai, Final Fantasy Crystal Chronicles)',
|
||||
'verified_at': None,
|
||||
}),
|
||||
dict({
|
||||
'name': 'What to expect:',
|
||||
'value': 'talking about various things i find interesting, and otherwise being a genuine and decent wholesome poster. i'm just here to hang out and talk to cool people! and to spill my thoughts.',
|
||||
'verified_at': None,
|
||||
}),
|
||||
]),
|
||||
'followers_count': 3169,
|
||||
'following_count': 328,
|
||||
'group': False,
|
||||
'header': 'https://files.mastodon.social/accounts/headers/000/014/715/original/5c6fc24edb3bb873.jpg',
|
||||
'header_static': 'https://files.mastodon.social/accounts/headers/000/014/715/original/5c6fc24edb3bb873.jpg',
|
||||
'hide_collections': True,
|
||||
'id': '14715',
|
||||
'indexable': False,
|
||||
'last_status_at': '2025-03-04T00:00:00',
|
||||
'limited': None,
|
||||
'locked': False,
|
||||
'memorial': None,
|
||||
'moved': None,
|
||||
'moved_to_account': None,
|
||||
'mute_expires_at': None,
|
||||
'noindex': False,
|
||||
'note': '<p>i have approximate knowledge of many things. perpetual student. (nb/ace/they)</p><p>xmpp/email: a@trwnh.com<br /><a href="https://trwnh.com" target="_blank" rel="nofollow noopener" translate="no"><span class="invisible">https://</span><span class="">trwnh.com</span><span class="invisible"></span></a><br />help me live:<br />- <a href="https://donate.stripe.com/4gwcPCaMpcQ19RC4gg" target="_blank" rel="nofollow noopener" translate="no"><span class="invisible">https://</span><span class="ellipsis">donate.stripe.com/4gwcPCaMpcQ1</span><span class="invisible">9RC4gg</span></a><br />- <a href="https://liberapay.com/trwnh" target="_blank" rel="nofollow noopener" translate="no"><span class="invisible">https://</span><span class="">liberapay.com/trwnh</span><span class="invisible"></span></a></p><p>notes:<br />- my triggers are moths and glitter<br />- i have all notifs except mentions turned off, so please interact if you wanna be friends! i literally will not notice otherwise<br />- dm me if i did something wrong, so i can improve<br />- purest person on fedi, do not lewd in my presence</p>',
|
||||
'role': None,
|
||||
'roles': list([
|
||||
]),
|
||||
'source': None,
|
||||
'statuses_count': 69523,
|
||||
'suspended': None,
|
||||
'uri': 'https://mastodon.social/users/trwnh',
|
||||
'url': 'https://mastodon.social/@trwnh',
|
||||
'username': 'trwnh',
|
||||
}),
|
||||
'instance': dict({
|
||||
'api_versions': None,
|
||||
'configuration': None,
|
||||
'contact': None,
|
||||
'description': 'The original server operated by the Mastodon gGmbH non-profit',
|
||||
'domain': 'mastodon.social',
|
||||
'icon': None,
|
||||
'languages': None,
|
||||
'registrations': None,
|
||||
'rules': None,
|
||||
'source_url': 'https://github.com/mastodon/mastodon',
|
||||
'thumbnail': None,
|
||||
'title': 'Mastodon',
|
||||
'uri': 'mastodon.social',
|
||||
'usage': dict({
|
||||
'users': dict({
|
||||
'active_month': 380143,
|
||||
}),
|
||||
}),
|
||||
'version': '4.4.0-nightly.2025-02-07',
|
||||
}),
|
||||
})
|
||||
# ---
|
||||
|
@@ -2,7 +2,11 @@
|
||||
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from mastodon.Mastodon import MastodonNetworkError, MastodonUnauthorizedError
|
||||
from mastodon.Mastodon import (
|
||||
MastodonNetworkError,
|
||||
MastodonNotFoundError,
|
||||
MastodonUnauthorizedError,
|
||||
)
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.mastodon.const import CONF_BASE_URL, DOMAIN
|
||||
@@ -80,6 +84,46 @@ async def test_full_flow_with_path(
|
||||
assert result["result"].unique_id == "trwnh_mastodon_social"
|
||||
|
||||
|
||||
async def test_full_flow_fallback_to_instance_v1(
|
||||
hass: HomeAssistant,
|
||||
mock_mastodon_client: AsyncMock,
|
||||
mock_setup_entry: AsyncMock,
|
||||
) -> None:
|
||||
"""Test full flow where instance_v2 fails and falls back to instance_v1."""
|
||||
mock_mastodon_client.instance_v2.side_effect = MastodonNotFoundError(
|
||||
"Instance API v2 not found"
|
||||
)
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_USER},
|
||||
)
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "user"
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
CONF_BASE_URL: "https://mastodon.social",
|
||||
CONF_CLIENT_ID: "client_id",
|
||||
CONF_CLIENT_SECRET: "client_secret",
|
||||
CONF_ACCESS_TOKEN: "access_token",
|
||||
},
|
||||
)
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result["title"] == "@trwnh@mastodon.social"
|
||||
assert result["data"] == {
|
||||
CONF_BASE_URL: "https://mastodon.social",
|
||||
CONF_CLIENT_ID: "client_id",
|
||||
CONF_CLIENT_SECRET: "client_secret",
|
||||
CONF_ACCESS_TOKEN: "access_token",
|
||||
}
|
||||
assert result["result"].unique_id == "trwnh_mastodon_social"
|
||||
|
||||
mock_mastodon_client.instance_v2.assert_called_once()
|
||||
mock_mastodon_client.instance_v1.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("exception", "error"),
|
||||
[
|
||||
|
@@ -2,6 +2,7 @@
|
||||
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from mastodon.Mastodon import MastodonNotFoundError
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
@@ -26,3 +27,26 @@ async def test_entry_diagnostics(
|
||||
await get_diagnostics_for_config_entry(hass, hass_client, mock_config_entry)
|
||||
== snapshot
|
||||
)
|
||||
|
||||
|
||||
async def test_entry_diagnostics_fallback_to_instance_v1(
|
||||
hass: HomeAssistant,
|
||||
hass_client: ClientSessionGenerator,
|
||||
mock_mastodon_client: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test config entry diagnostics with fallback to instance_v1 when instance_v2 raises MastodonNotFoundError."""
|
||||
mock_mastodon_client.instance_v2.side_effect = MastodonNotFoundError(
|
||||
"Instance v2 not found"
|
||||
)
|
||||
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
diagnostics_result = await get_diagnostics_for_config_entry(
|
||||
hass, hass_client, mock_config_entry
|
||||
)
|
||||
|
||||
mock_mastodon_client.instance_v1.assert_called()
|
||||
|
||||
assert diagnostics_result == snapshot
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from mastodon.Mastodon import MastodonError
|
||||
from mastodon.Mastodon import MastodonNotFoundError
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.mastodon.config_flow import MastodonConfigFlow
|
||||
@@ -39,13 +39,30 @@ async def test_initialization_failure(
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test initialization failure."""
|
||||
mock_mastodon_client.instance.side_effect = MastodonError
|
||||
mock_mastodon_client.instance_v1.side_effect = MastodonNotFoundError
|
||||
mock_mastodon_client.instance_v2.side_effect = MastodonNotFoundError
|
||||
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
|
||||
|
||||
|
||||
async def test_setup_integration_fallback_to_instance_v1(
|
||||
hass: HomeAssistant,
|
||||
mock_mastodon_client: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test full flow where instance_v2 fails and falls back to instance_v1."""
|
||||
mock_mastodon_client.instance_v2.side_effect = MastodonNotFoundError(
|
||||
"Instance API v2 not found"
|
||||
)
|
||||
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
mock_mastodon_client.instance_v2.assert_called_once()
|
||||
mock_mastodon_client.instance_v1.assert_called_once()
|
||||
|
||||
|
||||
async def test_migrate(
|
||||
hass: HomeAssistant,
|
||||
mock_mastodon_client: AsyncMock,
|
||||
|
Reference in New Issue
Block a user