Fix endpoint deprecation warning in Mastodon (#151275)

This commit is contained in:
Andrew Jackson
2025-08-28 10:59:37 +01:00
committed by GitHub
parent da65c52f2d
commit 5fbb99a79a
8 changed files with 205 additions and 11 deletions

View File

@@ -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

View File

@@ -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:

View File

@@ -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

View File

@@ -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

View File

@@ -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&#39;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&#39;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',
}),
})
# ---

View File

@@ -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"),
[

View File

@@ -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

View File

@@ -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,