diff --git a/pybotx/bot/bot.py b/pybotx/bot/bot.py index 86a8cc03..d200ce38 100644 --- a/pybotx/bot/bot.py +++ b/pybotx/bot/bot.py @@ -101,6 +101,10 @@ BotXAPIInternalBotNotificationRequestPayload, InternalBotNotificationMethod, ) +from pybotx.client.openid_api.refresh_access_token import ( + BotXAPIRefreshAccessTokenRequestPayload, + RefreshAccessTokenMethod, +) from pybotx.client.smartapps_api.smartapp_event import ( BotXAPISmartAppEventRequestPayload, SmartAppEventMethod, @@ -1658,6 +1662,34 @@ async def upload_file( return botx_api_async_file.to_domain() + # - OpenID API - + async def refresh_access_token( + self, + *, + bot_id: UUID, + huid: UUID, + ref: Optional[UUID] = None, + ) -> None: + """Refresh OpenID access token. + + :param bot_id: Bot which should perform the request. + :param huid: User huid. + :param ref: sync_id of the failed event to resend. + """ + + method = RefreshAccessTokenMethod( + bot_id, + self._httpx_client, + self._bot_accounts_storage, + self._callbacks_manager, + ) + + payload = BotXAPIRefreshAccessTokenRequestPayload.from_domain( + huid=huid, + ref=ref, + ) + await method.execute(payload) + @staticmethod def _build_main_collector( collectors: Sequence[HandlerCollector], diff --git a/pybotx/client/openid_api/__init__.py b/pybotx/client/openid_api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pybotx/client/openid_api/refresh_access_token.py b/pybotx/client/openid_api/refresh_access_token.py new file mode 100644 index 00000000..00928d22 --- /dev/null +++ b/pybotx/client/openid_api/refresh_access_token.py @@ -0,0 +1,45 @@ +from typing import Literal, Optional +from uuid import UUID + +from pybotx.client.authorized_botx_method import AuthorizedBotXMethod +from pybotx.models.api_base import UnverifiedPayloadBaseModel, VerifiedPayloadBaseModel + + +class BotXAPIRefreshAccessTokenRequestPayload(UnverifiedPayloadBaseModel): + user_huid: UUID + ref: Optional[UUID] + + @classmethod + def from_domain( + cls, + huid: UUID, + ref: Optional[UUID], + ) -> "BotXAPIRefreshAccessTokenRequestPayload": + return cls( + user_huid=huid, + ref=ref, + ) + + +class BotXAPIRefreshAccessTokenResponsePayload(VerifiedPayloadBaseModel): + status: Literal["ok"] + result: bool + + +class RefreshAccessTokenMethod(AuthorizedBotXMethod): + async def execute( + self, + payload: BotXAPIRefreshAccessTokenRequestPayload, + ) -> BotXAPIRefreshAccessTokenResponsePayload: + path = "/api/v3/botx/openid/refresh_access_token" + + response = await self._botx_method_call( + "POST", + self._build_url(path), + json=payload.jsonable_dict(), + ) + + return self._verify_and_extract_api_model( + BotXAPIRefreshAccessTokenResponsePayload, + response, + ) diff --git a/pyproject.toml b/pyproject.toml index c47c35c2..f9a20840 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pybotx" -version = "0.53.2" +version = "0.54.0" description = "A python library for interacting with eXpress BotX API" authors = [ "Sidnev Nikolay ", diff --git a/tests/client/openid_api/__init__.py b/tests/client/openid_api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/client/openid_api/test_resfesh_access_token.py b/tests/client/openid_api/test_resfesh_access_token.py new file mode 100644 index 00000000..b8f4f14f --- /dev/null +++ b/tests/client/openid_api/test_resfesh_access_token.py @@ -0,0 +1,52 @@ +from http import HTTPStatus +from uuid import UUID + +import httpx +import pytest +from respx.router import MockRouter + +from pybotx import Bot, BotAccountWithSecret, HandlerCollector, lifespan_wrapper + +pytestmark = [ + pytest.mark.asyncio, + pytest.mark.mock_authorization, + pytest.mark.usefixtures("respx_mock"), +] + + +async def test__refresh_access_token__succeed( + respx_mock: MockRouter, + host: str, + bot_id: UUID, + bot_account: BotAccountWithSecret, +) -> None: + # - Arrange - + endpoint = respx_mock.post( + f"https://{host}/api/v3/botx/openid/refresh_access_token", + headers={"Authorization": "Bearer token", "Content-Type": "application/json"}, + json={ + "user_huid": "a465f0f3-1354-491c-8f11-f400164295cb", + "ref": "a465f0f3-1354-491c-8f11-f400164295cb", + }, + ).mock( + return_value=httpx.Response( + HTTPStatus.ACCEPTED, + json={ + "status": "ok", + "result": True, + }, + ), + ) + + built_bot = Bot(collectors=[HandlerCollector()], bot_accounts=[bot_account]) + + # - Act - + async with lifespan_wrapper(built_bot) as bot: + await bot.refresh_access_token( + bot_id=bot_id, + huid=UUID("a465f0f3-1354-491c-8f11-f400164295cb"), + ref=UUID("a465f0f3-1354-491c-8f11-f400164295cb"), + ) + + # - Assert - + assert endpoint.called