Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions pybotx/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@
AttachmentTypes,
ChatLinkTypes,
ChatTypes,
ClientNetworkContours,
ClientPlatforms,
ConferenceLinkTypes,
MentionTypes,
Expand Down Expand Up @@ -221,6 +222,7 @@
"ChatNotFoundError",
"ChatLinkTypes",
"ChatTypes",
"ClientNetworkContours",
"ClientPlatforms",
"ConferenceChangedEvent",
"ConferenceCreatedEvent",
Expand Down
2 changes: 2 additions & 0 deletions pybotx/client/chats_api/create_chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ class BotXAPICreateChatRequestPayload(UnverifiedPayloadBaseModel):
@model_validator(mode="before")
def _convert_chat_type(cls, values: dict[str, Any]) -> dict[str, Any]:
chat_type = values.get("chat_type")
if isinstance(chat_type, APIChatTypes) and chat_type == APIChatTypes.VOEX_CALL:
raise ValueError("Bot cannot create a chat of type 'voex_call'")
if isinstance(chat_type, ChatTypes):
values["chat_type"] = convert_chat_type_from_domain(chat_type)
return values
Expand Down
29 changes: 29 additions & 0 deletions pybotx/models/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ class ClientPlatforms(AutoName):
AURORA = auto()


class ClientNetworkContours(AutoName):
INTERNAL = auto()
EXTERNAL = auto()


class MentionTypes(AutoName):
CONTACT = auto()
CHAT = auto()
Expand Down Expand Up @@ -103,6 +108,7 @@ class APIChatTypes(Enum):
GROUP_CHAT = "group_chat"
CHANNEL = "channel"
THREAD = "thread"
VOEX_CALL = "voex_call"


class BotAPICommandTypes(StrEnum):
Expand Down Expand Up @@ -136,6 +142,11 @@ class BotAPIClientPlatforms(Enum):
AURORA = "aurora"


class BotAPIClientNetworkContours(StrEnum):
INTERNAL = "internal"
EXTERNAL = "external"


class BotAPIEntityTypes(StrEnum):
MENTION = "mention"
FORWARD = "forward"
Expand Down Expand Up @@ -208,6 +219,23 @@ def convert_client_platform_to_domain(
return converted_type


def convert_client_network_contour_to_domain(
client_network_contour: BotAPIClientNetworkContours,
) -> ClientNetworkContours:
client_network_contours_mapping = {
BotAPIClientNetworkContours.INTERNAL: ClientNetworkContours.INTERNAL,
BotAPIClientNetworkContours.EXTERNAL: ClientNetworkContours.EXTERNAL,
}

converted_type = client_network_contours_mapping.get(client_network_contour)
if converted_type is None:
raise NotImplementedError(
f"Unsupported client network contour: {client_network_contour}",
)

return converted_type


def convert_mention_type_from_domain(
mention_type: MentionTypes,
) -> BotAPIMentionTypes:
Expand Down Expand Up @@ -338,6 +366,7 @@ def convert_chat_type_to_domain(
APIChatTypes.GROUP_CHAT: ChatTypes.GROUP_CHAT,
APIChatTypes.CHANNEL: ChatTypes.CHANNEL,
APIChatTypes.THREAD: ChatTypes.THREAD,
APIChatTypes.VOEX_CALL: ChatTypes.GROUP_CHAT,
}

converted_type: IncomingChatTypes | None
Expand Down
2 changes: 2 additions & 0 deletions pybotx/models/message/incoming_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from pybotx.models.enums import (
BotAPIEntityTypes,
BotAPIMentionTypes,
ClientNetworkContours,
ClientPlatforms,
convert_chat_type_to_domain,
convert_client_platform_to_domain,
Expand Down Expand Up @@ -68,6 +69,7 @@ class UserSender:
is_chat_admin: bool | None
is_chat_creator: bool | None
device: UserDevice
client_network_contour: ClientNetworkContours | None = None

@property
def upn(self) -> str | None:
Expand Down
15 changes: 13 additions & 2 deletions pybotx/models/sync_smartapp_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
from pybotx.models.chats import Chat
from pybotx.models.enums import (
BotAPIClientPlatforms,
BotAPIClientNetworkContours,
ChatTypes,
convert_client_network_contour_to_domain,
convert_client_platform_to_domain,
)
from pybotx.models.message.incoming_message import UserDevice, UserSender
Expand All @@ -25,6 +27,7 @@ class BotAPISyncSmartAppSender(VerifiedPayloadBaseModel):
user_huid: UUID
udid: UUID | None
platform: BotAPIClientPlatforms | None
client_network_contour: BotAPIClientNetworkContours | None = None


class BotAPISyncSmartAppPayload(VerifiedPayloadBaseModel):
Expand Down Expand Up @@ -59,6 +62,14 @@ def to_domain(self, raw_smartapp_event: dict[str, Any]) -> SmartAppEvent:
locale=None,
)

client_network_contour = (
convert_client_network_contour_to_domain(
self.sender_info.client_network_contour,
)
if self.sender_info.client_network_contour
else None
)

sender = UserSender(
huid=self.sender_info.user_huid,
udid=self.sender_info.udid,
Expand All @@ -68,6 +79,7 @@ def to_domain(self, raw_smartapp_event: dict[str, Any]) -> SmartAppEvent:
username=None,
is_chat_admin=None,
is_chat_creator=None,
client_network_contour=client_network_contour,
)

return SmartAppEvent(
Expand Down Expand Up @@ -143,6 +155,5 @@ def jsonable_dict(self) -> dict[str, Any]:


BotAPISyncSmartAppEventResponse = (
BotAPISyncSmartAppEventResultResponse
| BotAPISyncSmartAppEventErrorResponse
BotAPISyncSmartAppEventResultResponse | BotAPISyncSmartAppEventErrorResponse
)
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "pybotx"
version = "0.76.2"
version = "0.76.3"
description = "A python library for interacting with eXpress BotX API"
authors = [
"Sidnev Nikolay <nsidnev@ccsteam.ru>",
Expand Down
74 changes: 74 additions & 0 deletions tests/client/chats_api/test_chat_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,80 @@ async def test__chat_info__succeed(
assert endpoint.called


async def test__chat_info__succeed_voex_call(
respx_mock: MockRouter,
host: str,
bot_id: UUID,
datetime_formatter: Callable[[str], dt],
bot_factory: Any,
) -> None:
# - Arrange -
endpoint = mock_botx(
respx_mock,
host,
REQUEST,
ok_payload(
{
"chat_type": "voex_call",
"creator": "6fafda2c-6505-57a5-a088-25ea5d1d0364",
"description": None,
"group_chat_id": "054af49e-5e18-4dca-ad73-4f96b6de63fa",
"inserted_at": "2019-08-29T11:22:48.358586Z",
"members": [
{
"admin": True,
"user_huid": "6fafda2c-6505-57a5-a088-25ea5d1d0364",
"user_kind": "user",
},
{
"admin": False,
"user_huid": "705df263-6bfd-536a-9d51-13524afaab5c",
"user_kind": "botx",
},
],
"name": "Voex Chat Example",
"shared_history": False,
},
),
HTTPStatus.OK,
)

# - Act -
async with bot_factory() as bot:
chat_info = await bot.chat_info(
bot_id=bot_id,
chat_id=UUID("054af49e-5e18-4dca-ad73-4f96b6de63fa"),
)

# - Assert -
assert_deep_equal(
chat_info,
ChatInfo(
chat_type=ChatTypes.GROUP_CHAT,
creator_id=UUID("6fafda2c-6505-57a5-a088-25ea5d1d0364"),
description=None,
chat_id=UUID("054af49e-5e18-4dca-ad73-4f96b6de63fa"),
created_at=datetime_formatter("2019-08-29T11:22:48.358586Z"),
members=[
ChatInfoMember(
is_admin=True,
huid=UUID("6fafda2c-6505-57a5-a088-25ea5d1d0364"),
kind=UserKinds.RTS_USER,
),
ChatInfoMember(
is_admin=False,
huid=UUID("705df263-6bfd-536a-9d51-13524afaab5c"),
kind=UserKinds.BOT,
),
],
name="Voex Chat Example",
shared_history=False,
),
)

assert endpoint.called


async def test__chat_info__notes_chat_type_mapped_to_personal_chat(
respx_mock: MockRouter,
host: str,
Expand Down
7 changes: 6 additions & 1 deletion tests/client/chats_api/test_create_chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,12 @@ def test__create_chat_payload__convert_chat_type_validator() -> None:
result = BotXAPICreateChatRequestPayload._convert_chat_type(values) # type: ignore[operator]
assert result["chat_type"] == APIChatTypes.GROUP_CHAT

# Test with APIChatTypes value (should remain unchanged)
# Test with APIChatTypes.VOEX_CALL
values = {"chat_type": APIChatTypes.VOEX_CALL} # type: ignore[dict-item]
with pytest.raises(ValueError, match="Bot cannot create a chat of type 'voex_call'"):
BotXAPICreateChatRequestPayload._convert_chat_type(values) # type: ignore[operator]

# Test with another APIChatTypes value (should remain unchanged)
values = {"chat_type": APIChatTypes.CHAT} # type: ignore[dict-item]
result = BotXAPICreateChatRequestPayload._convert_chat_type(values) # type: ignore[operator]
assert result["chat_type"] == APIChatTypes.CHAT
Expand Down
24 changes: 24 additions & 0 deletions tests/models/test_enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@

from pybotx.models.enums import (
APIChatTypes,
BotAPIClientNetworkContours,
ClientNetworkContours,
ChatTypes,
convert_chat_type_from_domain,
convert_chat_type_to_domain,
convert_client_network_contour_to_domain,
)


Expand All @@ -31,4 +34,25 @@ def test__convert_chat_type_from_domain__unsupported_chat_type_raises_error() ->

def test__convert_chat_type_to_domain__notes_maps_to_personal_chat() -> None:
assert convert_chat_type_to_domain(APIChatTypes.NOTES) == ChatTypes.PERSONAL_CHAT
assert convert_chat_type_to_domain(APIChatTypes.VOEX_CALL) == ChatTypes.GROUP_CHAT
assert convert_chat_type_to_domain("notes") == ChatTypes.PERSONAL_CHAT


def test__convert_client_network_contour_to_domain__successful_conversion() -> None:
assert (
convert_client_network_contour_to_domain(BotAPIClientNetworkContours.INTERNAL)
== ClientNetworkContours.INTERNAL
)
assert (
convert_client_network_contour_to_domain(BotAPIClientNetworkContours.EXTERNAL)
== ClientNetworkContours.EXTERNAL
)


def test__convert_client_network_contour_to_domain__unsupported_contour_raises_error() -> (
None
):
unsupported_client_network_contour = Mock(spec=BotAPIClientNetworkContours)

with pytest.raises(NotImplementedError, match="Unsupported client network contour"):
convert_client_network_contour_to_domain(unsupported_client_network_contour)
49 changes: 49 additions & 0 deletions tests/models/test_sync_smartapp_event.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from typing import Any

import pytest

from pybotx.models.enums import ClientNetworkContours
from pybotx.models.sync_smartapp_event import BotAPISyncSmartAppEvent


@pytest.mark.parametrize(
("api_value", "domain_value"),
[
("internal", ClientNetworkContours.INTERNAL),
("external", ClientNetworkContours.EXTERNAL),
],
)
def test__sync_smartapp_event__client_network_contour_mapped_to_sender(
api_value: str,
domain_value: ClientNetworkContours,
) -> None:
payload = _sync_smartapp_event_payload(client_network_contour=api_value)

event = BotAPISyncSmartAppEvent.model_validate(payload).to_domain(payload)

assert event.sender.client_network_contour == domain_value


def _sync_smartapp_event_payload(
*,
client_network_contour: str,
) -> dict[str, Any]:
return {
"bot_id": "2a98219d-1f57-5dcb-920c-9a992bde01ec",
"group_chat_id": "1ee7fdcf-e258-03d6-2263-2764da127088",
"method": "menu",
"payload": {
"data": {
"camelCaseValue": "value2",
"under_score_value": "value1",
},
"files": [],
"opts": {},
},
"sender_info": {
"client_network_contour": client_network_contour,
"platform": "web",
"udid": "9eb0ed48-2501-59b8-9ba1-9136ff6efc59",
"user_huid": "347fdc52-fd0f-5e1d-b06f-bdfdf1cc7164",
},
}
Loading