From 76abcb528485383357eef1680be8c3d0887c2ab7 Mon Sep 17 00:00:00 2001 From: Robert Segal Date: Sun, 30 Nov 2025 11:07:22 -0700 Subject: [PATCH] Added Accounts users e2e tests --- mpt_api_client/resources/accounts/users.py | 10 +- tests/e2e/accounts/accounts_users/conftest.py | 36 ----- .../test_async_accounts_users.py | 125 ++++++++---------- .../test_sync_accounts_users.py | 31 ----- tests/e2e/accounts/conftest.py | 106 +++++++++++++++ tests/e2e/accounts/users/conftest.py | 25 ++++ tests/e2e/accounts/users/test_async_users.py | 70 ++++++++++ tests/e2e/accounts/users/test_sync_users.py | 82 ++++++++++++ 8 files changed, 346 insertions(+), 139 deletions(-) delete mode 100644 tests/e2e/accounts/accounts_users/conftest.py create mode 100644 tests/e2e/accounts/users/conftest.py create mode 100644 tests/e2e/accounts/users/test_async_users.py create mode 100644 tests/e2e/accounts/users/test_sync_users.py diff --git a/mpt_api_client/resources/accounts/users.py b/mpt_api_client/resources/accounts/users.py index fd1c6753..d8c322a2 100644 --- a/mpt_api_client/resources/accounts/users.py +++ b/mpt_api_client/resources/accounts/users.py @@ -3,11 +3,11 @@ AsyncCollectionMixin, AsyncDeleteMixin, AsyncGetMixin, - AsyncUpdateMixin, + AsyncUpdateFileMixin, CollectionMixin, DeleteMixin, GetMixin, - UpdateMixin, + UpdateFileMixin, ) from mpt_api_client.models import Model from mpt_api_client.models.model import ResourceData @@ -27,10 +27,12 @@ class UsersServiceConfig: _endpoint = "/public/v1/accounts/users" _model_class = User _collection_key = "data" + _upload_file_key = "icon" + _upload_data_key = "user" class UsersService( - UpdateMixin[User], + UpdateFileMixin[User], DeleteMixin, BlockableMixin[User], GetMixin[User], @@ -71,7 +73,7 @@ def set_password(self, resource_id: str, password: str) -> User: class AsyncUsersService( - AsyncUpdateMixin[User], + AsyncUpdateFileMixin[User], AsyncDeleteMixin, AsyncBlockableMixin[User], AsyncGetMixin[User], diff --git a/tests/e2e/accounts/accounts_users/conftest.py b/tests/e2e/accounts/accounts_users/conftest.py deleted file mode 100644 index 689d37f5..00000000 --- a/tests/e2e/accounts/accounts_users/conftest.py +++ /dev/null @@ -1,36 +0,0 @@ -import pytest - - -@pytest.fixture -def invalid_user_id(): - return "USR-0000-0000" - - -@pytest.fixture -def account_user_factory(account_id, user_group_id, uuid_str): - def _account_user( # noqa: WPS430 - email: str | None = None, # Must be unique in Marketplace - first_name: str = "E2E Created", - last_name: str = "Account User", - ): - if not email: - email = f"e2e_{uuid_str}@dummy.com" - - return { - "user": { - "firstName": first_name, - "lastName": last_name, - "email": email, - }, - "account": { - "id": account_id, - }, - "groups": [ - {"id": user_group_id}, - ], - "invitation": { - "status": "Invited", - }, - } - - return _account_user diff --git a/tests/e2e/accounts/accounts_users/test_async_accounts_users.py b/tests/e2e/accounts/accounts_users/test_async_accounts_users.py index 1c66c754..7a594190 100644 --- a/tests/e2e/accounts/accounts_users/test_async_accounts_users.py +++ b/tests/e2e/accounts/accounts_users/test_async_accounts_users.py @@ -11,38 +11,6 @@ def users(async_mpt_vendor, account_id): return async_mpt_vendor.accounts.accounts.users(account_id=account_id) # noqa: WPS204 -@pytest.fixture -async def created_account_user(async_mpt_vendor, account_user_factory, account_id): - """Fixture to create and teardown an account user.""" - ret_account_user = None - - async def _created_account_user( - first_name: str = "E2E Created", - last_name: str = "Account User", - ): - """Create an account user with the given first and last name.""" - nonlocal ret_account_user # noqa: WPS420 - account_user_data = account_user_factory( - first_name=first_name, - last_name=last_name, - ) - users_obj = async_mpt_vendor.accounts.accounts.users(account_id=account_id) - ret_account_user = await users_obj.create(account_user_data) - return ret_account_user - - yield _created_account_user - - if ret_account_user: - users_obj = async_mpt_vendor.accounts.accounts.users(account_id=account_id) - try: - await users_obj.delete(ret_account_user.id) - except MPTAPIError as error: - print( # noqa: WPS421 - f"TEARDOWN - Unable to delete account user {ret_account_user.id}: " - f"{getattr(error, 'title', str(error))}" - ) - - @pytest.fixture async def created_user_group(async_mpt_ops, user_group_factory): """Fixture to create and teardown a user group.""" @@ -61,10 +29,10 @@ async def created_user_group(async_mpt_ops, user_group_factory): @pytest.fixture async def created_account_user_group( # noqa: WPS210 - async_mpt_vendor, created_account_user, created_user_group, account_id + async_mpt_vendor, async_created_account_user, created_user_group, account_id ): """Fixture to create and teardown an account user group.""" - created_account_user_data = await created_account_user() + created_account_user_data = await async_created_account_user() user_group_data = created_user_group create_user_group_data = {"id": user_group_data.id} users = async_mpt_vendor.accounts.accounts.users(account_id=account_id) @@ -85,115 +53,136 @@ async def created_account_user_group( # noqa: WPS210 async def test_get_account_user_by_id(async_mpt_vendor, user_id, account_id): """Test retrieving an account user by ID.""" users_obj = async_mpt_vendor.accounts.accounts.users(account_id=account_id) + account_user = await users_obj.get(user_id) + assert account_user is not None async def test_list_account_users(async_mpt_vendor, account_id): """Test listing account users.""" limit = 10 - users_obj = async_mpt_vendor.accounts.accounts.users(account_id=account_id) - account_users = await users_obj.fetch_page(limit=limit) - assert len(account_users) > 0 + result = await users_obj.fetch_page(limit=limit) + + assert len(result) > 0 async def test_get_account_user_by_id_not_found(async_mpt_vendor, invalid_user_id, account_id): """Test retrieving an account user by invalid ID.""" - users_obj = async_mpt_vendor.accounts.accounts.users(account_id=account_id) + result = async_mpt_vendor.accounts.accounts.users(account_id=account_id) with pytest.raises(MPTAPIError, match=r"404 Not Found"): - await users_obj.get(invalid_user_id) + await result.get(invalid_user_id) async def test_filter_account_users(async_mpt_vendor, user_id, account_id): """Test filtering account users.""" select_fields = ["-name"] - users_obj = async_mpt_vendor.accounts.accounts.users(account_id=account_id) filtered_account_users = users_obj.filter(RQLQuery(id=user_id)).select(*select_fields) - account_users = [user async for user in filtered_account_users.iterate()] - assert len(account_users) == 1 + result = [user async for user in filtered_account_users.iterate()] + assert len(result) == 1 -async def test_create_account_user(created_account_user): + +async def test_create_account_user(async_created_account_user): """Test creating an account user.""" - account_user_data = await created_account_user() - assert account_user_data is not None + result = await async_created_account_user() + + assert result is not None -async def test_delete_account_user(async_mpt_vendor, created_account_user, account_id): +async def test_delete_account_user(async_mpt_vendor, async_created_account_user, account_id): """Test deleting an account user.""" - account_user_data = await created_account_user() - users_obj = async_mpt_vendor.accounts.accounts.users(account_id=account_id) - await users_obj.delete(account_user_data.id) + account_user_data = await async_created_account_user() + + result = async_mpt_vendor.accounts.accounts.users(account_id=account_id) + + await result.delete(account_user_data.id) async def test_update_account_user( async_mpt_vendor, account_user_factory, - created_account_user, + async_created_account_user, account_id, ): """Test updating an account user.""" - account_user_data = await created_account_user() + account_user_data = await async_created_account_user() + users_obj = async_mpt_vendor.accounts.accounts.users(account_id=account_id) + updated_account_user_data = account_user_factory( first_name="E2E Updated", last_name="Account User", ) - updated_account_user = await users_obj.update( + + result = await users_obj.update( account_user_data.id, updated_account_user_data, ) - assert updated_account_user is not None + + assert result is not None async def test_account_user_resend_invite( async_mpt_vendor, - created_account_user, + async_created_account_user, account_id, ): """Test resending an invite to an account user.""" - account_user_data = await created_account_user() - users_obj = async_mpt_vendor.accounts.accounts.users(account_id=account_id) - resend_invite = await users_obj.resend_invite(account_user_data.id) - assert resend_invite is not None + account_user_data = await async_created_account_user() + + result = async_mpt_vendor.accounts.accounts.users(account_id=account_id) + + result = await result.resend_invite(account_user_data.id) + + assert result is not None def test_account_user_group_post(created_account_user_group): # noqa: AAA01 """Test creating an account user group.""" - created_account_user_group_data = created_account_user_group - assert created_account_user_group_data is not None + result = created_account_user_group + + assert result is not None async def test_account_user_group_update( async_mpt_vendor, - created_account_user, + async_created_account_user, created_user_group, account_id, ): """Test updating an account user group.""" - created_account_user_data = await created_account_user() + created_account_user_data = await async_created_account_user() + users_obj = async_mpt_vendor.accounts.accounts.users(account_id=account_id) + update_user_group_data = [{"id": created_user_group.id}] - updated_account_user_group = await users_obj.groups( - user_id=created_account_user_data.id - ).update(update_user_group_data) - assert updated_account_user_group is not None + + result = await users_obj.groups(user_id=created_account_user_data.id).update( + update_user_group_data + ) + + assert result is not None async def test_account_user_group_delete( async_mpt_vendor, - created_account_user, + async_created_account_user, created_user_group, account_id, ): """Test deleting an account user group.""" - created_account_user_data = await created_account_user() + created_account_user_data = await async_created_account_user() + users_obj = async_mpt_vendor.accounts.accounts.users(account_id=account_id) + create_user_group_data = {"id": created_user_group.id} + await users_obj.groups(user_id=created_account_user_data.id).create(create_user_group_data) + await users_obj.groups(user_id=created_account_user_data.id).delete(created_user_group.id) diff --git a/tests/e2e/accounts/accounts_users/test_sync_accounts_users.py b/tests/e2e/accounts/accounts_users/test_sync_accounts_users.py index a107adb4..68f3f57e 100644 --- a/tests/e2e/accounts/accounts_users/test_sync_accounts_users.py +++ b/tests/e2e/accounts/accounts_users/test_sync_accounts_users.py @@ -11,37 +11,6 @@ def users(mpt_vendor, account_id): return mpt_vendor.accounts.accounts.users(account_id=account_id) # noqa: WPS204 -@pytest.fixture -def created_account_user(mpt_vendor, account_user_factory, account_id): - """Fixture to create and teardown an account user.""" - ret_account_user = None - - def _created_account_user( - first_name: str = "E2E Created", - last_name: str = "Account User", - ): - """Create an account user with the given first and last name.""" - nonlocal ret_account_user # noqa: WPS420 - account_user_data = account_user_factory( - first_name=first_name, - last_name=last_name, - ) - users_obj = mpt_vendor.accounts.accounts.users(account_id=account_id) - ret_account_user = users_obj.create(account_user_data) - return ret_account_user - - yield _created_account_user - - if ret_account_user: - users_obj = mpt_vendor.accounts.accounts.users(account_id=account_id) - try: - users_obj.delete(ret_account_user.id) - except MPTAPIError: - print( # noqa: WPS421 - f"TEARDOWN - Unable to delete account user {ret_account_user.id}", - ) - - @pytest.fixture def created_user_group(mpt_ops, user_group_factory): """Fixture to create and teardown a user group.""" diff --git a/tests/e2e/accounts/conftest.py b/tests/e2e/accounts/conftest.py index a7a1fa7e..aa08c88e 100644 --- a/tests/e2e/accounts/conftest.py +++ b/tests/e2e/accounts/conftest.py @@ -2,12 +2,20 @@ import pytest +from mpt_api_client.exceptions import MPTAPIError + @pytest.fixture(scope="session") def timestamp(): return int(dt.datetime.now(tz=dt.UTC).strftime("%Y%m%d%H%M%S")) +@pytest.fixture +def invalid_user_id(): + # Used in e2e tests for two resources (users and accounts-users) + return "USR-0000-0000" + + @pytest.fixture def account_icon(logo_fd): return logo_fd @@ -43,6 +51,11 @@ def module_id(e2e_config): return e2e_config["accounts.module.id"] +@pytest.fixture +def user_id(e2e_config): + return e2e_config["accounts.user.id"] + + @pytest.fixture def user_group_factory(account_id, module_id): # Used in user group and licensee fixtures @@ -60,3 +73,96 @@ def _user_group( } return _user_group + + +@pytest.fixture +def account_user_factory(account_id, user_group_id, uuid_str): + def _account_user( # noqa: WPS430 + email: str | None = None, # Must be unique in Marketplace + first_name: str = "E2E Created", + last_name: str = "Account User", + ): + if not email: + email = f"e2e_{uuid_str}@dummy.com" + + return { + "user": { + "firstName": first_name, + "lastName": last_name, + "email": email, + }, + "account": { + "id": account_id, + }, + "groups": [ + {"id": user_group_id}, + ], + "invitation": { + "status": "Invited", + }, + } + + return _account_user + + +@pytest.fixture +def created_account_user(mpt_vendor, account_user_factory, account_id): + """Fixture to create and teardown an account user used in two resources.""" + ret_account_user = None + + def _created_account_user( + first_name: str = "E2E Created", + last_name: str = "Account User", + ): + """Create an account user with the given first and last name.""" + nonlocal ret_account_user # noqa: WPS420 + account_user_data = account_user_factory( + first_name=first_name, + last_name=last_name, + ) + users_obj = mpt_vendor.accounts.accounts.users(account_id=account_id) + ret_account_user = users_obj.create(account_user_data) + return ret_account_user + + yield _created_account_user + + if ret_account_user: + users_obj = mpt_vendor.accounts.accounts.users(account_id=account_id) + try: + users_obj.delete(ret_account_user.id) + except MPTAPIError: + print( # noqa: WPS421 + f"TEARDOWN - Unable to delete account user {ret_account_user.id}", + ) + + +@pytest.fixture +async def async_created_account_user(async_mpt_vendor, account_user_factory, account_id): + """Fixture to create and teardown an account user used in two resources.""" + ret_account_user = None + + async def _created_account_user( + first_name: str = "E2E Created", + last_name: str = "Account User", + ): + """Create an account user with the given first and last name.""" + nonlocal ret_account_user # noqa: WPS420 + account_user_data = account_user_factory( + first_name=first_name, + last_name=last_name, + ) + users_obj = async_mpt_vendor.accounts.accounts.users(account_id=account_id) + ret_account_user = await users_obj.create(account_user_data) + return ret_account_user + + yield _created_account_user + + if ret_account_user: + users_obj = async_mpt_vendor.accounts.accounts.users(account_id=account_id) + try: + await users_obj.delete(ret_account_user.id) + except MPTAPIError as error: + print( # noqa: WPS421 + f"TEARDOWN - Unable to delete account user {ret_account_user.id}: " + f"{getattr(error, 'title', str(error))}" + ) diff --git a/tests/e2e/accounts/users/conftest.py b/tests/e2e/accounts/users/conftest.py new file mode 100644 index 00000000..cbd82513 --- /dev/null +++ b/tests/e2e/accounts/users/conftest.py @@ -0,0 +1,25 @@ +import pytest + + +@pytest.fixture +def user_factory(uuid_str): + def _user( # noqa: WPS430 + email: str | None = None, # Must be unique in Marketplace + first_name: str = "E2E Created", + last_name: str = "User", + ): + if not email: + email = f"e2e_{uuid_str}@dummy.com" + + return { + "firstName": first_name, + "lastName": last_name, + "email": email, + "status": "Invited", + "settings": { + "cultureCode": "en-US", + "languageCode": "en-US", + }, + } + + return _user diff --git a/tests/e2e/accounts/users/test_async_users.py b/tests/e2e/accounts/users/test_async_users.py new file mode 100644 index 00000000..df2d12d8 --- /dev/null +++ b/tests/e2e/accounts/users/test_async_users.py @@ -0,0 +1,70 @@ +import pytest + +from mpt_api_client.exceptions import MPTAPIError +from mpt_api_client.rql.query_builder import RQLQuery + +pytestmark = [pytest.mark.flaky] + + +async def test_get_user_by_id(async_mpt_vendor, user_id): + """Test retrieving a user by ID.""" + users_service = async_mpt_vendor.accounts.users + result = await users_service.get(user_id) + assert result is not None + + +async def test_get_user_by_invalid_id(async_mpt_vendor, invalid_user_id): + """Test retrieving a user by an invalid ID.""" + users_service = async_mpt_vendor.accounts.users + with pytest.raises(MPTAPIError, match=r"404 Not Found"): + await users_service.get(invalid_user_id) + + +async def test_filter_account_users(async_mpt_vendor, user_id): + """Test filtering users within an account.""" + select_fields = ["-name"] + users_service = async_mpt_vendor.accounts.users + filtered_users = users_service.filter(RQLQuery(id=user_id)).select(*select_fields) + result = [user async for user in filtered_users.iterate()] + assert len(result) == 1 + + +async def test_update_user(async_mpt_ops, async_created_account_user, user_factory, account_icon): + """Test updating a user.""" + created_user = await async_created_account_user() + updated_data = user_factory( + first_name="E2E Updated", + last_name="User", + ) + users_service = async_mpt_ops.accounts.users + result = await users_service.update(created_user.id, updated_data, file=account_icon) + assert result.first_name == "E2E Updated" + assert result.last_name == "User" + + +async def test_update_user_not_found(async_mpt_vendor, invalid_user_id, user_factory, account_icon): + """Test updating a user that does not exist.""" + updated_data = user_factory( + first_name="E2E Updated", + last_name="User", + ) + users_service = async_mpt_vendor.accounts.users + with pytest.raises(MPTAPIError, match=r"404 Not Found"): + await users_service.update(invalid_user_id, updated_data, file=account_icon) + + +async def test_user_block(async_mpt_ops, async_created_account_user): + """Test blocking a user.""" + created_user = await async_created_account_user() + users_service = async_mpt_ops.accounts.users + result = await users_service.block(created_user.id) + assert result is not None + + +async def test_user_unblock(async_mpt_ops, async_created_account_user): + """Test unblocking a user.""" + created_user = await async_created_account_user() + users_service = async_mpt_ops.accounts.users + await users_service.block(created_user.id) + result = await users_service.unblock(created_user.id) + assert result is not None diff --git a/tests/e2e/accounts/users/test_sync_users.py b/tests/e2e/accounts/users/test_sync_users.py new file mode 100644 index 00000000..b45ed1e9 --- /dev/null +++ b/tests/e2e/accounts/users/test_sync_users.py @@ -0,0 +1,82 @@ +import pytest + +from mpt_api_client.exceptions import MPTAPIError +from mpt_api_client.rql.query_builder import RQLQuery + +pytestmark = [pytest.mark.flaky] + + +def test_get_user_by_id(mpt_vendor, user_id): + """Test retrieving a user by ID.""" + users_service = mpt_vendor.accounts.users + + result = users_service.get(user_id) + + assert result is not None + + +def test_get_user_by_invalid_id(mpt_vendor, invalid_user_id): + """Test retrieving a user by an invalid ID.""" + users_service = mpt_vendor.accounts.users + + with pytest.raises(MPTAPIError, match=r"404 Not Found"): + users_service.get(invalid_user_id) + + +def test_filter_account_users(mpt_vendor, user_id): + """Test filtering users within an account.""" + select_fields = ["-name"] + users_service = mpt_vendor.accounts.users + filtered_users = users_service.filter(RQLQuery(id=user_id)).select(*select_fields) + + result = list(filtered_users.iterate()) + + assert len(result) == 1 + + +def test_update_user(mpt_ops, created_account_user, user_factory, account_icon): + """Test updating a user.""" + created_user = created_account_user() + updated_data = user_factory( + first_name="E2E Updated", + last_name="User", + ) + users_service = mpt_ops.accounts.users + + result = users_service.update(created_user.id, updated_data, file=account_icon) + + assert result.first_name == "E2E Updated" + assert result.last_name == "User" + + +def test_update_user_not_found(mpt_vendor, invalid_user_id, user_factory, account_icon): + """Test updating a user that does not exist.""" + updated_data = user_factory( + first_name="E2E Updated", + last_name="User", + ) + users_service = mpt_vendor.accounts.users + + with pytest.raises(MPTAPIError, match=r"404 Not Found"): + users_service.update(invalid_user_id, updated_data, file=account_icon) + + +def test_user_block(mpt_ops, created_account_user): + """Test blocking a user.""" + created_user = created_account_user() + users_service = mpt_ops.accounts.users + + result = users_service.block(created_user.id) + + assert result is not None + + +def test_user_unblock(mpt_ops, created_account_user): + """Test unblocking a user.""" + created_user = created_account_user() + users_service = mpt_ops.accounts.users + users_service.block(created_user.id) + + result = users_service.unblock(created_user.id) + + assert result is not None