diff --git a/pybotx/client/users_api/user_from_search.py b/pybotx/client/users_api/user_from_search.py index 7af8c492..8075b3d9 100644 --- a/pybotx/client/users_api/user_from_search.py +++ b/pybotx/client/users_api/user_from_search.py @@ -1,3 +1,4 @@ +from datetime import datetime from typing import List, Literal, Optional from uuid import UUID @@ -19,6 +20,18 @@ class BotXAPISearchUserResult(VerifiedPayloadBaseModel): emails: List[str] = Field(default_factory=list) other_id: Optional[str] = None user_kind: APIUserKinds + active: Optional[bool] = None + description: Optional[str] = None + ip_phone: Optional[int] = None + manager: Optional[str] = None + office: Optional[str] = None + other_ip_phone: Optional[int] = None + other_phone: Optional[int] = None + public_name: Optional[str] = None + cts_id: Optional[UUID] = None + rts_id: Optional[UUID] = None + created_at: Optional[datetime] = None + updated_at: Optional[datetime] = None class BotXAPISearchUserResponsePayload(VerifiedPayloadBaseModel): @@ -37,6 +50,18 @@ def to_domain(self) -> UserFromSearch: emails=self.result.emails, other_id=self.result.other_id, user_kind=convert_user_kind_to_domain(self.result.user_kind), + active=None if self.result.active is None else bool(self.result.active), + description=self.result.description, + ip_phone=self.result.ip_phone, + manager=self.result.manager, + office=self.result.office, + other_ip_phone=self.result.other_ip_phone, + other_phone=self.result.other_phone, + public_name=self.result.public_name, + cts_id=self.result.cts_id, + rts_id=self.result.rts_id, + created_at=self.result.created_at, + updated_at=self.result.updated_at, ) @@ -57,6 +82,18 @@ def to_domain(self) -> List[UserFromSearch]: emails=user.emails, other_id=user.other_id, user_kind=convert_user_kind_to_domain(user.user_kind), + active=None if user.active is None else bool(user.active), + created_at=user.created_at, + cts_id=user.cts_id, + description=user.description, + ip_phone=user.ip_phone, + manager=user.manager, + office=user.office, + other_ip_phone=user.other_ip_phone, + other_phone=user.other_phone, + public_name=user.public_name, + rts_id=user.rts_id, + updated_at=user.updated_at, ) for user in self.result ] diff --git a/pybotx/models/users.py b/pybotx/models/users.py index 1eb91380..cad8cdab 100644 --- a/pybotx/models/users.py +++ b/pybotx/models/users.py @@ -1,4 +1,5 @@ from dataclasses import dataclass +from datetime import datetime from typing import List, Optional from uuid import UUID @@ -20,6 +21,18 @@ class UserFromSearch: emails: User emails. other_id: User other identificator. user_kind: User kind. + active: User active status. + description: User description. + ip_phone: User IP phone. + manager: User manager. + office: User office. + other_ip_phone: User other IP phone. + other_phone: User other phone. + public_name: User public name. + cts_id: User CTS id. + rts_id: User RTS id. + created_at: User creation timestamp. + updated_at: User update timestamp. """ huid: UUID @@ -32,6 +45,18 @@ class UserFromSearch: emails: List[str] other_id: Optional[str] user_kind: UserKinds + active: Optional[bool] = None + description: Optional[str] = None + ip_phone: Optional[int] = None + manager: Optional[str] = None + office: Optional[str] = None + other_ip_phone: Optional[int] = None + other_phone: Optional[int] = None + public_name: Optional[str] = None + cts_id: Optional[UUID] = None + rts_id: Optional[UUID] = None + created_at: Optional[datetime] = None + updated_at: Optional[datetime] = None @dataclass diff --git a/pyproject.toml b/pyproject.toml index 2e0d1506..e0d0bd79 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pybotx" -version = "0.69.1" +version = "0.70.0" description = "A python library for interacting with eXpress BotX API" authors = [ "Sidnev Nikolay ", diff --git a/tests/client/users_api/conftest.py b/tests/client/users_api/conftest.py new file mode 100644 index 00000000..9ac1bb35 --- /dev/null +++ b/tests/client/users_api/conftest.py @@ -0,0 +1,117 @@ +from typing import Any, Dict +from uuid import UUID + +import pytest + +from pybotx import UserFromSearch, UserKinds +from tests.client.users_api.convert_to_datetime import convert_to_datetime + + +@pytest.fixture() +def user_from_search_with_data_json() -> Dict[str, Any]: + return { + "user_huid": "6fafda2c-6505-57a5-a088-25ea5d1d0364", + "ad_login": "ad_user_login", + "ad_domain": "cts.com", + "name": "Bob", + "company": "Bobs Co", + "company_position": "Director", + "department": "Owners", + "emails": ["ad_user@cts.com"], + "user_kind": "cts_user", + "active": True, + "created_at": "2023-03-26T14:36:08.740618Z", + "cts_id": "e0140f4c-4af2-5a2e-9ad1-5f37fceafbaf", + "description": "Director in Owners dep", + "ip_phone": 1271020, + "manager": "Alice", + "office": "SUN", + "other_ip_phone": 32593, + "other_phone": 1254218, + "public_name": "Bobby", + "rts_id": "f46440a4-d930-58d4-b3f5-8110ab846ee3", + "updated_at": "2023-03-26T14:36:08.740618Z", + } + + +@pytest.fixture +def user_from_search_with_data() -> UserFromSearch: + return UserFromSearch( + huid=UUID("6fafda2c-6505-57a5-a088-25ea5d1d0364"), + ad_login="ad_user_login", + ad_domain="cts.com", + username="Bob", + company="Bobs Co", + company_position="Director", + department="Owners", + emails=["ad_user@cts.com"], + other_id=None, + user_kind=UserKinds.CTS_USER, + active=True, + created_at=convert_to_datetime("2023-03-26T14:36:08.740618Z"), + cts_id=UUID("e0140f4c-4af2-5a2e-9ad1-5f37fceafbaf"), + description="Director in Owners dep", + ip_phone=1271020, + manager="Alice", + office="SUN", + other_ip_phone=32593, + other_phone=1254218, + public_name="Bobby", + rts_id=UUID("f46440a4-d930-58d4-b3f5-8110ab846ee3"), + updated_at=convert_to_datetime("2023-03-26T14:36:08.740618Z"), + ) + + +@pytest.fixture +def user_from_search_without_data_json() -> Dict[str, Any]: + return { + "user_huid": "6fafda2c-6505-57a5-a088-25ea5d1d0364", + "ad_login": "ad_user_login", + "ad_domain": "cts.com", + "name": "Bob", + "company": "Bobs Co", + "company_position": "Director", + "department": "Owners", + "emails": ["ad_user@cts.com"], + "user_kind": "cts_user", + "active": None, + "created_at": None, + "cts_id": None, + "description": None, + "ip_phone": None, + "manager": None, + "office": None, + "other_ip_phone": None, + "other_phone": None, + "public_name": None, + "rts_id": None, + "updated_at": None, + } + + +@pytest.fixture +def user_from_search_without_data() -> UserFromSearch: + return UserFromSearch( + huid=UUID("6fafda2c-6505-57a5-a088-25ea5d1d0364"), + ad_login="ad_user_login", + ad_domain="cts.com", + username="Bob", + company="Bobs Co", + company_position="Director", + department="Owners", + emails=["ad_user@cts.com"], + other_id=None, + user_kind=UserKinds.CTS_USER, + active=None, + created_at=None, + cts_id=None, + description=None, + ip_phone=None, + manager=None, + office=None, + other_ip_phone=None, + other_phone=None, + public_name=None, + rts_id=None, + updated_at=None, + ) diff --git a/tests/client/users_api/convert_to_datetime.py b/tests/client/users_api/convert_to_datetime.py new file mode 100644 index 00000000..788bd108 --- /dev/null +++ b/tests/client/users_api/convert_to_datetime.py @@ -0,0 +1,9 @@ +from datetime import datetime, timezone + + +def convert_to_datetime(str_datetime: str) -> datetime: + datetime_instance = datetime.strptime( + str_datetime, + "%Y-%m-%dT%H:%M:%S.%fZ", # noqa: WPS325 + ) + return datetime_instance.replace(tzinfo=timezone.utc) diff --git a/tests/client/users_api/test_search_user_by_email.py b/tests/client/users_api/test_search_user_by_email.py index 2813d3e6..fc59f1ef 100644 --- a/tests/client/users_api/test_search_user_by_email.py +++ b/tests/client/users_api/test_search_user_by_email.py @@ -1,4 +1,5 @@ from http import HTTPStatus +from typing import Any, Dict from uuid import UUID import httpx @@ -13,7 +14,6 @@ UserNotFoundError, lifespan_wrapper, ) -from pybotx.models.enums import UserKinds pytestmark = [ pytest.mark.asyncio, @@ -65,6 +65,8 @@ async def test__search_user_by_email__succeed( host: str, bot_id: UUID, bot_account: BotAccountWithSecret, + user_from_search_with_data: UserFromSearch, + user_from_search_with_data_json: Dict[str, Any], ) -> None: # - Arrange - endpoint = respx_mock.get( @@ -76,17 +78,7 @@ async def test__search_user_by_email__succeed( HTTPStatus.OK, json={ "status": "ok", - "result": { - "user_huid": "6fafda2c-6505-57a5-a088-25ea5d1d0364", - "ad_login": "ad_user_login", - "ad_domain": "cts.com", - "name": "Bob", - "company": "Bobs Co", - "company_position": "Director", - "department": "Owners", - "emails": ["ad_user@cts.com"], - "user_kind": "cts_user", - }, + "result": user_from_search_with_data_json, }, ), ) @@ -101,17 +93,44 @@ async def test__search_user_by_email__succeed( ) # - Assert - - assert user == UserFromSearch( - huid=UUID("6fafda2c-6505-57a5-a088-25ea5d1d0364"), - ad_login="ad_user_login", - ad_domain="cts.com", - username="Bob", - company="Bobs Co", - company_position="Director", - department="Owners", - emails=["ad_user@cts.com"], - other_id=None, - user_kind=UserKinds.CTS_USER, + assert user == user_from_search_with_data + + assert endpoint.called + + +async def test__search_user_by_email_without_extra_data__succeed( + respx_mock: MockRouter, + host: str, + bot_id: UUID, + bot_account: BotAccountWithSecret, + user_from_search_without_data: UserFromSearch, + user_from_search_without_data_json: Dict[str, Any], +) -> None: + # - Arrange - + endpoint = respx_mock.get( + f"https://{host}/api/v3/botx/users/by_email", + headers={"Authorization": "Bearer token"}, + params={"email": "ad_user@cts.com"}, + ).mock( + return_value=httpx.Response( + HTTPStatus.OK, + json={ + "status": "ok", + "result": user_from_search_without_data_json, + }, + ), ) + built_bot = Bot(collectors=[HandlerCollector()], bot_accounts=[bot_account]) + + # - Act - + async with lifespan_wrapper(built_bot) as bot: + user = await bot.search_user_by_email( + bot_id=bot_id, + email="ad_user@cts.com", + ) + + # - Assert - + assert user == user_from_search_without_data + assert endpoint.called diff --git a/tests/client/users_api/test_search_user_by_emails.py b/tests/client/users_api/test_search_user_by_emails.py index aeb23290..f521209a 100644 --- a/tests/client/users_api/test_search_user_by_emails.py +++ b/tests/client/users_api/test_search_user_by_emails.py @@ -1,4 +1,5 @@ from http import HTTPStatus +from typing import Any, Dict from uuid import UUID import httpx @@ -12,7 +13,6 @@ UserFromSearch, lifespan_wrapper, ) -from pybotx.models.enums import UserKinds pytestmark = [ pytest.mark.asyncio, @@ -26,6 +26,8 @@ async def test__search_user_by_email__succeed( host: str, bot_id: UUID, bot_account: BotAccountWithSecret, + user_from_search_with_data: UserFromSearch, + user_from_search_with_data_json: Dict[str, Any], ) -> None: # - Arrange - user_emails = ["ad_user@cts.com"] @@ -39,19 +41,7 @@ async def test__search_user_by_email__succeed( HTTPStatus.OK, json={ "status": "ok", - "result": [ - { - "user_huid": "6fafda2c-6505-57a5-a088-25ea5d1d0364", - "ad_login": "ad_user_login", - "ad_domain": "cts.com", - "name": "Bob", - "company": "Bobs Co", - "company_position": "Director", - "department": "Owners", - "emails": user_emails, - "user_kind": "cts_user", - }, - ], + "result": [user_from_search_with_data_json], }, ), ) @@ -66,17 +56,46 @@ async def test__search_user_by_email__succeed( ) # - Assert - - assert users[0] == UserFromSearch( - huid=UUID("6fafda2c-6505-57a5-a088-25ea5d1d0364"), - ad_login="ad_user_login", - ad_domain="cts.com", - username="Bob", - company="Bobs Co", - company_position="Director", - department="Owners", - emails=user_emails, - other_id=None, - user_kind=UserKinds.CTS_USER, + assert users[0] == user_from_search_with_data + + assert endpoint.called + + +async def test__search_user_by_email_without_data__succeed( + respx_mock: MockRouter, + host: str, + bot_id: UUID, + bot_account: BotAccountWithSecret, + user_from_search_without_data: UserFromSearch, + user_from_search_without_data_json: Dict[str, Any], +) -> None: + # - Arrange - + user_emails = ["ad_user@cts.com"] + + endpoint = respx_mock.post( + f"https://{host}/api/v3/botx/users/by_email", + headers={"Authorization": "Bearer token", "Content-Type": "application/json"}, + json={"emails": user_emails}, + ).mock( + return_value=httpx.Response( + HTTPStatus.OK, + json={ + "status": "ok", + "result": [user_from_search_without_data_json], + }, + ), ) + built_bot = Bot(collectors=[HandlerCollector()], bot_accounts=[bot_account]) + + # - Act - + async with lifespan_wrapper(built_bot) as bot: + users = await bot.search_user_by_emails( + bot_id=bot_id, + emails=user_emails, + ) + + # - Assert - + assert users[0] == user_from_search_without_data + assert endpoint.called diff --git a/tests/client/users_api/test_search_user_by_huid.py b/tests/client/users_api/test_search_user_by_huid.py index 03417c7e..7396497b 100644 --- a/tests/client/users_api/test_search_user_by_huid.py +++ b/tests/client/users_api/test_search_user_by_huid.py @@ -1,4 +1,5 @@ from http import HTTPStatus +from typing import Any, Dict from uuid import UUID import httpx @@ -13,7 +14,6 @@ UserNotFoundError, lifespan_wrapper, ) -from pybotx.models.enums import UserKinds pytestmark = [ pytest.mark.asyncio, @@ -65,6 +65,8 @@ async def test__search_user_by_huid__succeed( host: str, bot_id: UUID, bot_account: BotAccountWithSecret, + user_from_search_with_data: UserFromSearch, + user_from_search_with_data_json: Dict[str, Any], ) -> None: # - Arrange - endpoint = respx_mock.get( @@ -76,17 +78,7 @@ async def test__search_user_by_huid__succeed( HTTPStatus.OK, json={ "status": "ok", - "result": { - "user_huid": "f837dff4-d3ad-4b8d-a0a3-5c6ca9c747d1", - "ad_login": "ad_user_login", - "ad_domain": "cts.com", - "name": "Bob", - "company": "Bobs Co", - "company_position": "Director", - "department": "Owners", - "emails": ["ad_user@cts.com"], - "user_kind": "cts_user", - }, + "result": user_from_search_with_data_json, }, ), ) @@ -101,17 +93,44 @@ async def test__search_user_by_huid__succeed( ) # - Assert - - assert user == UserFromSearch( - huid=UUID("f837dff4-d3ad-4b8d-a0a3-5c6ca9c747d1"), - ad_login="ad_user_login", - ad_domain="cts.com", - username="Bob", - company="Bobs Co", - company_position="Director", - department="Owners", - emails=["ad_user@cts.com"], - other_id=None, - user_kind=UserKinds.CTS_USER, + assert user == user_from_search_with_data + + assert endpoint.called + + +async def test__search_user_by_huid_without_data__succeed( + respx_mock: MockRouter, + host: str, + bot_id: UUID, + bot_account: BotAccountWithSecret, + user_from_search_without_data: UserFromSearch, + user_from_search_without_data_json: Dict[str, Any], +) -> None: + # - Arrange - + endpoint = respx_mock.get( + f"https://{host}/api/v3/botx/users/by_huid", + headers={"Authorization": "Bearer token"}, + params={"user_huid": "f837dff4-d3ad-4b8d-a0a3-5c6ca9c747d1"}, + ).mock( + return_value=httpx.Response( + HTTPStatus.OK, + json={ + "status": "ok", + "result": user_from_search_without_data_json, + }, + ), ) + built_bot = Bot(collectors=[HandlerCollector()], bot_accounts=[bot_account]) + + # - Act - + async with lifespan_wrapper(built_bot) as bot: + user = await bot.search_user_by_huid( + bot_id=bot_id, + huid=UUID("f837dff4-d3ad-4b8d-a0a3-5c6ca9c747d1"), + ) + + # - Assert - + assert user == user_from_search_without_data + assert endpoint.called diff --git a/tests/client/users_api/test_search_user_by_login.py b/tests/client/users_api/test_search_user_by_login.py index 97595ef3..238d692c 100644 --- a/tests/client/users_api/test_search_user_by_login.py +++ b/tests/client/users_api/test_search_user_by_login.py @@ -1,4 +1,5 @@ from http import HTTPStatus +from typing import Any, Dict from uuid import UUID import httpx @@ -13,7 +14,6 @@ UserNotFoundError, lifespan_wrapper, ) -from pybotx.models.enums import UserKinds pytestmark = [ pytest.mark.asyncio, @@ -66,6 +66,8 @@ async def test__search_user_by_ad__succeed( host: str, bot_id: UUID, bot_account: BotAccountWithSecret, + user_from_search_with_data: UserFromSearch, + user_from_search_with_data_json: Dict[str, Any], ) -> None: # - Arrange - endpoint = respx_mock.get( @@ -77,17 +79,7 @@ async def test__search_user_by_ad__succeed( HTTPStatus.OK, json={ "status": "ok", - "result": { - "user_huid": "6fafda2c-6505-57a5-a088-25ea5d1d0364", - "ad_login": "ad_user_login", - "ad_domain": "cts.com", - "name": "Bob", - "company": "Bobs Co", - "company_position": "Director", - "department": "Owners", - "emails": ["ad_user@cts.com"], - "user_kind": "cts_user", - }, + "result": user_from_search_with_data_json, }, ), ) @@ -103,17 +95,45 @@ async def test__search_user_by_ad__succeed( ) # - Assert - - assert user == UserFromSearch( - huid=UUID("6fafda2c-6505-57a5-a088-25ea5d1d0364"), - ad_login="ad_user_login", - ad_domain="cts.com", - username="Bob", - company="Bobs Co", - company_position="Director", - department="Owners", - emails=["ad_user@cts.com"], - other_id=None, - user_kind=UserKinds.CTS_USER, + assert user == user_from_search_with_data + + assert endpoint.called + + +async def test__search_user_by_ad_without_data__succeed( + respx_mock: MockRouter, + host: str, + bot_id: UUID, + bot_account: BotAccountWithSecret, + user_from_search_without_data: UserFromSearch, + user_from_search_without_data_json: Dict[str, Any], +) -> None: + # - Arrange - + endpoint = respx_mock.get( + f"https://{host}/api/v3/botx/users/by_login", + headers={"Authorization": "Bearer token"}, + params={"ad_login": "ad_user_login", "ad_domain": "cts.com"}, + ).mock( + return_value=httpx.Response( + HTTPStatus.OK, + json={ + "status": "ok", + "result": user_from_search_without_data_json, + }, + ), ) + built_bot = Bot(collectors=[HandlerCollector()], bot_accounts=[bot_account]) + + # - Act - + async with lifespan_wrapper(built_bot) as bot: + user = await bot.search_user_by_ad( + bot_id=bot_id, + ad_login="ad_user_login", + ad_domain="cts.com", + ) + + # - Assert - + assert user == user_from_search_without_data + assert endpoint.called diff --git a/tests/client/users_api/test_search_user_by_other_id.py b/tests/client/users_api/test_search_user_by_other_id.py index 2f8c6001..de58acf1 100644 --- a/tests/client/users_api/test_search_user_by_other_id.py +++ b/tests/client/users_api/test_search_user_by_other_id.py @@ -1,4 +1,5 @@ from http import HTTPStatus +from typing import Any, Dict from uuid import UUID import httpx @@ -13,7 +14,6 @@ UserNotFoundError, lifespan_wrapper, ) -from pybotx.models.enums import UserKinds pytestmark = [ pytest.mark.asyncio, @@ -65,6 +65,8 @@ async def test__search_user_by_other_id__succeed( host: str, bot_id: UUID, bot_account: BotAccountWithSecret, + user_from_search_with_data: UserFromSearch, + user_from_search_with_data_json: Dict[str, Any], ) -> None: # - Arrange - endpoint = respx_mock.get( @@ -76,18 +78,7 @@ async def test__search_user_by_other_id__succeed( HTTPStatus.OK, json={ "status": "ok", - "result": { - "user_huid": "6fafda2c-6505-57a5-a088-25ea5d1d0364", - "ad_login": "ad_user_login", - "ad_domain": "cts.com", - "name": "Bob", - "company": "Bobs Co", - "company_position": "Director", - "department": "Owners", - "emails": ["ad_user@cts.com"], - "other_id": "some_id", - "user_kind": "cts_user", - }, + "result": user_from_search_with_data_json, }, ), ) @@ -102,17 +93,44 @@ async def test__search_user_by_other_id__succeed( ) # - Assert - - assert user == UserFromSearch( - huid=UUID("6fafda2c-6505-57a5-a088-25ea5d1d0364"), - ad_login="ad_user_login", - ad_domain="cts.com", - username="Bob", - company="Bobs Co", - company_position="Director", - department="Owners", - emails=["ad_user@cts.com"], - other_id="some_id", - user_kind=UserKinds.CTS_USER, + assert user == user_from_search_with_data + + assert endpoint.called + + +async def test__search_user_by_other_id_without_data__succeed( + respx_mock: MockRouter, + host: str, + bot_id: UUID, + bot_account: BotAccountWithSecret, + user_from_search_without_data: UserFromSearch, + user_from_search_without_data_json: Dict[str, Any], +) -> None: + # - Arrange - + endpoint = respx_mock.get( + f"https://{host}/api/v3/botx/users/by_other_id", + headers={"Authorization": "Bearer token"}, + params={"other_id": "some_id"}, + ).mock( + return_value=httpx.Response( + HTTPStatus.OK, + json={ + "status": "ok", + "result": user_from_search_without_data_json, + }, + ), ) + built_bot = Bot(collectors=[HandlerCollector()], bot_accounts=[bot_account]) + + # - Act - + async with lifespan_wrapper(built_bot) as bot: + user = await bot.search_user_by_other_id( + bot_id=bot_id, + other_id="some_id", + ) + + # - Assert - + assert user == user_from_search_without_data + assert endpoint.called