mirror of
https://github.com/home-assistant/core.git
synced 2025-09-09 06:41:33 +02:00
Fix endpoint deprecation warning in Mastodon (#151275)
This commit is contained in:
@@ -2,7 +2,14 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
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 (
|
from homeassistant.const import (
|
||||||
CONF_ACCESS_TOKEN,
|
CONF_ACCESS_TOKEN,
|
||||||
@@ -105,7 +112,11 @@ def setup_mastodon(
|
|||||||
entry.data[CONF_ACCESS_TOKEN],
|
entry.data[CONF_ACCESS_TOKEN],
|
||||||
)
|
)
|
||||||
|
|
||||||
instance = client.instance()
|
try:
|
||||||
|
instance = client.instance_v2()
|
||||||
|
except MastodonNotFoundError:
|
||||||
|
instance = client.instance_v1()
|
||||||
|
|
||||||
account = client.account_verify_credentials()
|
account = client.account_verify_credentials()
|
||||||
|
|
||||||
return client, instance, account
|
return client, instance, account
|
||||||
|
@@ -7,7 +7,9 @@ from typing import Any
|
|||||||
from mastodon.Mastodon import (
|
from mastodon.Mastodon import (
|
||||||
Account,
|
Account,
|
||||||
Instance,
|
Instance,
|
||||||
|
InstanceV2,
|
||||||
MastodonNetworkError,
|
MastodonNetworkError,
|
||||||
|
MastodonNotFoundError,
|
||||||
MastodonUnauthorizedError,
|
MastodonUnauthorizedError,
|
||||||
)
|
)
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
@@ -61,7 +63,7 @@ class MastodonConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
client_secret: str,
|
client_secret: str,
|
||||||
access_token: str,
|
access_token: str,
|
||||||
) -> tuple[
|
) -> tuple[
|
||||||
Instance | None,
|
InstanceV2 | Instance | None,
|
||||||
Account | None,
|
Account | None,
|
||||||
dict[str, str],
|
dict[str, str],
|
||||||
]:
|
]:
|
||||||
@@ -73,7 +75,10 @@ class MastodonConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
client_secret,
|
client_secret,
|
||||||
access_token,
|
access_token,
|
||||||
)
|
)
|
||||||
instance = client.instance()
|
try:
|
||||||
|
instance = client.instance_v2()
|
||||||
|
except MastodonNotFoundError:
|
||||||
|
instance = client.instance_v1()
|
||||||
account = client.account_verify_credentials()
|
account = client.account_verify_credentials()
|
||||||
|
|
||||||
except MastodonNetworkError:
|
except MastodonNetworkError:
|
||||||
|
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from mastodon.Mastodon import Account, Instance
|
from mastodon.Mastodon import Account, Instance, InstanceV2, MastodonNotFoundError
|
||||||
|
|
||||||
from homeassistant.core import HomeAssistant
|
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."""
|
"""Get mastodon diagnostics."""
|
||||||
client = config_entry.runtime_data.client
|
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()
|
account = client.account_verify_credentials()
|
||||||
|
|
||||||
return instance, account
|
return instance, account
|
||||||
|
@@ -32,12 +32,16 @@ def mock_mastodon_client() -> Generator[AsyncMock]:
|
|||||||
) as mock_client,
|
) as mock_client,
|
||||||
):
|
):
|
||||||
client = mock_client.return_value
|
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)
|
load_fixture("instance.json", DOMAIN)
|
||||||
)
|
)
|
||||||
client.account_verify_credentials.return_value = Account.from_json(
|
client.account_verify_credentials.return_value = Account.from_json(
|
||||||
load_fixture("account_verify_credentials.json", DOMAIN)
|
load_fixture("account_verify_credentials.json", DOMAIN)
|
||||||
)
|
)
|
||||||
|
client.mastodon_api_version = 2
|
||||||
client.status_post.return_value = None
|
client.status_post.return_value = None
|
||||||
yield client
|
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 unittest.mock import AsyncMock
|
||||||
|
|
||||||
from mastodon.Mastodon import MastodonNetworkError, MastodonUnauthorizedError
|
from mastodon.Mastodon import (
|
||||||
|
MastodonNetworkError,
|
||||||
|
MastodonNotFoundError,
|
||||||
|
MastodonUnauthorizedError,
|
||||||
|
)
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.mastodon.const import CONF_BASE_URL, DOMAIN
|
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"
|
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(
|
@pytest.mark.parametrize(
|
||||||
("exception", "error"),
|
("exception", "error"),
|
||||||
[
|
[
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from unittest.mock import AsyncMock
|
from unittest.mock import AsyncMock
|
||||||
|
|
||||||
|
from mastodon.Mastodon import MastodonNotFoundError
|
||||||
from syrupy.assertion import SnapshotAssertion
|
from syrupy.assertion import SnapshotAssertion
|
||||||
|
|
||||||
from homeassistant.core import HomeAssistant
|
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)
|
await get_diagnostics_for_config_entry(hass, hass_client, mock_config_entry)
|
||||||
== snapshot
|
== 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 unittest.mock import AsyncMock
|
||||||
|
|
||||||
from mastodon.Mastodon import MastodonError
|
from mastodon.Mastodon import MastodonNotFoundError
|
||||||
from syrupy.assertion import SnapshotAssertion
|
from syrupy.assertion import SnapshotAssertion
|
||||||
|
|
||||||
from homeassistant.components.mastodon.config_flow import MastodonConfigFlow
|
from homeassistant.components.mastodon.config_flow import MastodonConfigFlow
|
||||||
@@ -39,13 +39,30 @@ async def test_initialization_failure(
|
|||||||
mock_config_entry: MockConfigEntry,
|
mock_config_entry: MockConfigEntry,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test initialization failure."""
|
"""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)
|
await setup_integration(hass, mock_config_entry)
|
||||||
|
|
||||||
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
|
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(
|
async def test_migrate(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
mock_mastodon_client: AsyncMock,
|
mock_mastodon_client: AsyncMock,
|
||||||
|
Reference in New Issue
Block a user