From 49dc78dfa9ddc8ddbe073ff0965daea5d16a0075 Mon Sep 17 00:00:00 2001 From: tr4nt0r <4445816+tr4nt0r@users.noreply.github.com> Date: Tue, 21 Jan 2025 21:05:00 +0100 Subject: [PATCH] Add method `get_lists_users` --- bring_api/bring.py | 58 ++++++++++++++++++++ bring_api/types.py | 21 ++++++++ tests/__snapshots__/test_bring.ambr | 3 ++ tests/conftest.py | 32 +++++++++++ tests/test_bring.py | 84 +++++++++++++++++++++++++++++ 5 files changed, 198 insertions(+) diff --git a/bring_api/bring.py b/bring_api/bring.py index 407c618..22155b9 100644 --- a/bring_api/bring.py +++ b/bring_api/bring.py @@ -45,6 +45,7 @@ BringNotificationType, BringSyncCurrentUserResponse, BringUserSettingsResponse, + BringUsersResponse, UserLocale, ) @@ -1513,3 +1514,60 @@ async def get_activity(self, list_uuid: str) -> BringActivityResponse: raise BringRequestException( "Loading list activity failed due to request exception." ) from e + + async def get_list_users(self, list_uuid: str) -> BringUsersResponse: + """Retrieve members of a shared list.""" + + try: + url = self.url / "v2/bringlists" / list_uuid / "users" + async with self._session.get(url, headers=self.headers) as r: + _LOGGER.debug( + "Response from %s [%s]: %s", url, r.status, await r.text() + ) + + if r.status == HTTPStatus.UNAUTHORIZED: + try: + errmsg = BringErrorResponse.from_json(await r.text()) + except (JSONDecodeError, aiohttp.ClientError): + _LOGGER.debug( + "Exception: Cannot parse request response:", exc_info=True + ) + else: + _LOGGER.debug( + "Exception: Cannot get list users: %s", errmsg.message + ) + raise BringAuthException( + "Loading list users failed due to authorization failure, " + "the authorization token is invalid or expired." + ) + + r.raise_for_status() + + try: + return BringUsersResponse.from_json(await r.text()) + except MissingField as e: + raise BringMissingFieldException(e) from e + except (JSONDecodeError, KeyError) as e: + _LOGGER.debug( + "Exception: Cannot get users for list %s:", + list_uuid, + exc_info=True, + ) + raise BringParseException( + "Loading list users failed during parsing of request response." + ) from e + + except TimeoutError as e: + _LOGGER.debug( + "Exception: Cannot get users for list %s:", list_uuid, exc_info=True + ) + raise BringRequestException( + "Loading list users failed due to connection timeout." + ) from e + except aiohttp.ClientError as e: + _LOGGER.debug( + "Exception: Cannot get users for list %s:", list_uuid, exc_info=True + ) + raise BringRequestException( + "Loading list users failed due to request exception." + ) from e diff --git a/bring_api/types.py b/bring_api/types.py index 5b207b4..99a81f8 100644 --- a/bring_api/types.py +++ b/bring_api/types.py @@ -278,3 +278,24 @@ class BringErrorResponse(DataClassORJSONMixin): error: str error_description: str errorcode: int + + +@dataclass(kw_only=True) +class BringUser: + """A Bring user.""" + + publicUuid: str + pushEnabled: bool + plusTryOut: bool + country: str + language: str + name: str = "" + email: str = "" + photoPath: str = "" + + +@dataclass(kw_only=True) +class BringUsersResponse(DataClassORJSONMixin): + """List users.""" + + users: list[BringUser] = field(default_factory=list) diff --git a/tests/__snapshots__/test_bring.ambr b/tests/__snapshots__/test_bring.ambr index 0cdfb34..9e583d7 100644 --- a/tests/__snapshots__/test_bring.ambr +++ b/tests/__snapshots__/test_bring.ambr @@ -11,6 +11,9 @@ # name: TestGetList.test_get_list BringItemsResponse(uuid='00000000-0000-0000-0000-000000000000', status='SHARED', items=Items(purchase=[BringPurchase(uuid='43bdd5a2-740a-4230-8b27-d0bbde886da7', itemId='Paprika', specification='grün', attributes=[]), BringPurchase(uuid='2de9d1c0-c211-4129-b6c5-c1260c3fc735', itemId='Zucchetti', specification='gelb', attributes=[])], recently=[BringPurchase(uuid='5681ed79-c8e4-4c8b-95ec-112999d016c0', itemId='Paprika', specification='rot', attributes=[]), BringPurchase(uuid='01eea2cd-f433-4263-ad08-3d71317c4298', itemId='Pouletbrüstli', specification='', attributes=[])])) # --- +# name: TestGetListUsers.test_get_lists_users + BringUsersResponse(users=[BringUser(publicUuid='98615d7e-0a7d-4a7e-8f73-a9cbb9f1bc32', pushEnabled=True, plusTryOut=False, country='DE', language='de', name='NAME', email='EMAIL', photoPath=''), BringUser(publicUuid='73af455f-c158-4004-a5e0-79f4f8a6d4bd', pushEnabled=True, plusTryOut=False, country='US', language='en', name='NAME', email='EMAIL', photoPath=''), BringUser(publicUuid='7d5e9d08-877a-4c36-8740-a9bf74ec690a', pushEnabled=True, plusTryOut=False, country='US', language='en', name='', email='', photoPath='')]) +# --- # name: TestGetUserAccount.test_get_user_account BringSyncCurrentUserResponse(email='{email}', emailVerified=True, premiumConfiguration={'hasPremium': False, 'hideSponsoredProducts': False, 'hideSponsoredTemplates': False, 'hideSponsoredPosts': False, 'hideSponsoredCategories': False, 'hideOffersOnMain': False}, publicUserUuid='00000000-0000-0000-0000-000000000000', userLocale=UserLocale(language='de', country='DE'), userUuid='00000000-0000-0000-0000-000000000000', name='{user_name}', photoPath='bring/user/portrait/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx') # --- diff --git a/tests/conftest.py b/tests/conftest.py index d5bf1e0..2276245 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -199,6 +199,38 @@ "totalEvents": 2, } +BRING_GET_LIST_USERS_RESPONSE = { + "users": [ + { + "publicUuid": "98615d7e-0a7d-4a7e-8f73-a9cbb9f1bc32", + "name": "NAME", + "email": "EMAIL", + "photoPath": "", + "pushEnabled": True, + "plusTryOut": False, + "country": "DE", + "language": "de", + }, + { + "publicUuid": "73af455f-c158-4004-a5e0-79f4f8a6d4bd", + "name": "NAME", + "email": "EMAIL", + "photoPath": "", + "pushEnabled": True, + "plusTryOut": False, + "country": "US", + "language": "en", + }, + { + "publicUuid": "7d5e9d08-877a-4c36-8740-a9bf74ec690a", + "pushEnabled": True, + "plusTryOut": False, + "country": "US", + "language": "en", + }, + ] +} + BRING_ERROR_RESPONSE = { "message": "", "error": "", diff --git a/tests/test_bring.py b/tests/test_bring.py index 0473173..8614018 100644 --- a/tests/test_bring.py +++ b/tests/test_bring.py @@ -6,6 +6,7 @@ import time import aiohttp +from aioresponses import aioresponses from dotenv import load_dotenv import pytest from syrupy.assertion import SnapshotAssertion @@ -35,6 +36,7 @@ BRING_GET_ACTIVITY_RESPONSE, BRING_GET_ALL_ITEM_DETAILS_RESPONSE, BRING_GET_LIST_RESPONSE, + BRING_GET_LIST_USERS_RESPONSE, BRING_LOAD_LISTS_RESPONSE, BRING_LOGIN_RESPONSE, BRING_TOKEN_RESPONSE, @@ -1613,3 +1615,85 @@ async def test_parse_exception(self, mocked, bring, status, exception): with pytest.raises(exception): await bring.get_activity(UUID) + + +class TestGetListUsers: + """Tests for get_list_users method.""" + + async def test_get_lists_users( + self, + bring: Bring, + mocked: aioresponses, + monkeypatch: pytest.MonkeyPatch, + snapshot: SnapshotAssertion, + ): + """Test get_list_users.""" + + mocked.get( + f"https://api.getbring.com/rest/v2/bringlists/{UUID}/users", + status=HTTPStatus.OK, + payload=BRING_GET_LIST_USERS_RESPONSE, + ) + monkeypatch.setattr(bring, "uuid", UUID) + + activity = await bring.get_list_users(UUID) + + assert activity == snapshot + + @pytest.mark.parametrize( + "exception", + [ + asyncio.TimeoutError, + aiohttp.ClientError, + ], + ) + async def test_request_exception( + self, mocked: aioresponses, bring: Bring, exception: Exception + ): + """Test request exceptions.""" + + mocked.get( + f"https://api.getbring.com/rest/v2/bringlists/{UUID}/users", + exception=exception, + ) + + with pytest.raises(BringRequestException): + await bring.get_list_users(UUID) + + async def test_auth_exception(self, mocked: aioresponses, bring: Bring): + """Test request exceptions.""" + + mocked.get( + f"https://api.getbring.com/rest/v2/bringlists/{UUID}/users", + status=HTTPStatus.UNAUTHORIZED, + payload=BRING_ERROR_RESPONSE, + ) + + with pytest.raises(BringAuthException): + await bring.get_list_users(UUID) + + @pytest.mark.parametrize( + ("status", "exception"), + [ + (HTTPStatus.OK, BringParseException), + (HTTPStatus.UNAUTHORIZED, BringAuthException), + ], + ) + async def test_parse_exception( + self, + mocked: aioresponses, + bring: Bring, + status: HTTPStatus, + exception: Exception, + ): + """Test request exceptions.""" + + mocked.get( + f"https://api.getbring.com/rest/v2/bringlists/{UUID}/users", + status=status, + body="not json", + content_type="application/json", + ) + + with pytest.raises(exception): + await bring.get_list_users(UUID)