From 98f135b3b5e8ab2658e019366853f76ffe142c9e Mon Sep 17 00:00:00 2001 From: paulnoirel <87332996+paulnoirel@users.noreply.github.com> Date: Wed, 2 Jul 2025 20:10:35 +0100 Subject: [PATCH 1/5] Fix _get_members_set to get role name --- .../src/labelbox/schema/user_group.py | 18 +- .../tests/unit/schema/test_user_group.py | 164 +++++++++++------- 2 files changed, 113 insertions(+), 69 deletions(-) diff --git a/libs/labelbox/src/labelbox/schema/user_group.py b/libs/labelbox/src/labelbox/schema/user_group.py index 5abd7b03a..1956a9f52 100644 --- a/libs/labelbox/src/labelbox/schema/user_group.py +++ b/libs/labelbox/src/labelbox/schema/user_group.py @@ -679,6 +679,12 @@ def _get_members_set( member_nodes = members_data.get("nodes", []) user_group_roles = members_data.get("userGroupRoles", []) + # Get all roles to map IDs to names + from labelbox.schema.role import get_roles + + all_roles = get_roles(self.client) + role_id_to_role = {role.uid: role for role in all_roles.values()} + # Create a mapping from userId to roleId user_role_mapping = { role_data["userId"]: role_data["roleId"] @@ -694,15 +700,9 @@ def _get_members_set( # Get the role for this user from the mapping role_id = user_role_mapping.get(node["id"]) - if role_id: - # We need to fetch the role details since we only have the roleId - # For now, create a minimal Role object with just the ID - role_values: defaultdict[str, Any] = defaultdict(lambda: None) - role_values["id"] = role_id - # We don't have the role name from this response, so we'll leave it as None - # The Role object will fetch the name when needed - role = Role(self.client, role_values) - + if role_id and role_id in role_id_to_role: + # Use the actual Role object with proper name resolution + role = role_id_to_role[role_id] members.add(UserGroupMember(user=user, role=role)) return members diff --git a/libs/labelbox/tests/unit/schema/test_user_group.py b/libs/labelbox/tests/unit/schema/test_user_group.py index 40b653da4..5492f0a2c 100644 --- a/libs/labelbox/tests/unit/schema/test_user_group.py +++ b/libs/labelbox/tests/unit/schema/test_user_group.py @@ -100,6 +100,11 @@ def test_user_group_color_values(self): class TestUserGroup: def setup_method(self): + # Reset the global roles cache before each test + import labelbox.schema.role as role_module + + role_module._ROLES = None + self.client = MagicMock(Client) self.client.get_roles.return_value = { "LABELER": Role(self.client, {"id": "role_id", "name": "LABELER"}), @@ -167,26 +172,36 @@ def test_get(self): "orgRole": {"id": "role_id_2", "name": "LABELER"}, }, ] - self.client.execute.return_value = { - "userGroupV2": { - "id": "group_id", - "name": "Test Group", - "color": "4ED2F9", - "description": "", - "projects": { - "nodes": projects, - "totalCount": 2, - }, - "members": { - "nodes": group_members, - "totalCount": 2, - "userGroupRoles": [ - {"userId": "user_id_1", "roleId": "role_id_1"}, - {"userId": "user_id_2", "roleId": "role_id_2"}, - ], - }, - } - } + self.client.execute.side_effect = [ + # Mock userGroupV2 query response first (get() executes this first) + { + "userGroupV2": { + "id": "group_id", + "name": "Test Group", + "color": "4ED2F9", + "description": "", + "projects": { + "nodes": projects, + "totalCount": 2, + }, + "members": { + "nodes": group_members, + "totalCount": 2, + "userGroupRoles": [ + {"userId": "user_id_1", "roleId": "role_id_1"}, + {"userId": "user_id_2", "roleId": "role_id_2"}, + ], + }, + } + }, + # Mock get_roles query response second (_get_members_set calls this) + { + "roles": [ + {"id": "role_id_1", "name": "LABELER"}, + {"id": "role_id_2", "name": "REVIEWER"}, + ] + }, + ] group = UserGroup(self.client) assert group.id == "" assert group.name == "" @@ -244,7 +259,7 @@ def test_update(self, group_user, group_project, mock_role): } } }, - # Mock get query response after update + # Mock get query response after update (get() executes userGroupV2 first) { "userGroupV2": { "id": "group_id", @@ -270,6 +285,12 @@ def test_update(self, group_user, group_project, mock_role): }, } }, + # Mock get_roles query response (called by _get_members_set) + { + "roles": [ + {"id": "role_id", "name": "LABELER"}, + ] + }, ] group.update() @@ -297,7 +318,7 @@ def test_update_without_members_should_work(self, group_project): } } }, - # Mock get query response + # Mock get query response (get() executes userGroupV2 first) { "userGroupV2": { "id": "group_id", @@ -315,6 +336,8 @@ def test_update_without_members_should_work(self, group_project): }, } }, + # Mock get_roles query response (even though no members, _get_members_set is still called) + {"roles": []}, ] group.update() @@ -356,42 +379,51 @@ def test_user_groups_empty(self): assert len(user_groups) == 0 def test_user_groups(self): - self.client.execute.return_value = { - "userGroupsV2": { - "totalCount": 2, - "nextCursor": None, - "nodes": [ - { - "id": "group_id_1", - "name": "Group 1", - "color": "9EC5FF", - "description": "", - "projects": { - "nodes": [], - "totalCount": 0, - }, - "members": { - "nodes": [], - "totalCount": 0, - }, - }, - { - "id": "group_id_2", - "name": "Group 2", - "color": "CEB8FF", - "description": "", - "projects": { - "nodes": [], - "totalCount": 0, + # Mock get_user_groups and get_roles responses + # The order is: userGroupsV2 query first, then get_roles when processing groups + self.client.execute.side_effect = [ + # get_user_groups query (first) + { + "userGroupsV2": { + "totalCount": 2, + "nextCursor": None, + "nodes": [ + { + "id": "group_id_1", + "name": "Group 1", + "color": "9EC5FF", + "description": "", + "projects": { + "nodes": [], + "totalCount": 0, + }, + "members": { + "nodes": [], + "totalCount": 0, + "userGroupRoles": [], + }, }, - "members": { - "nodes": [], - "totalCount": 0, + { + "id": "group_id_2", + "name": "Group 2", + "color": "CEB8FF", + "description": "", + "projects": { + "nodes": [], + "totalCount": 0, + }, + "members": { + "nodes": [], + "totalCount": 0, + "userGroupRoles": [], + }, }, - }, - ], - } - } + ], + } + }, + # get_roles query (called when processing first group, then cached) + {"roles": []}, + ] user_groups = list(UserGroup.get_user_groups(self.client)) assert len(user_groups) == 2 assert user_groups[0].name == "Group 1" @@ -448,7 +480,7 @@ def test_create(self, group_user, group_project, mock_role): } } }, - # Mock get query response after create + # Mock get query response after create (get() executes userGroupV2 first) { "userGroupV2": { "id": "group_id", @@ -474,6 +506,12 @@ def test_create(self, group_user, group_project, mock_role): }, } }, + # Mock get_roles query response (called by _get_members_set) + { + "roles": [ + {"id": "role_id", "name": "LABELER"}, + ] + }, ] group.create() @@ -501,7 +539,7 @@ def test_create_without_members_should_work(self, group_project): } } }, - # Mock get query response + # Mock get query response (get() executes userGroupV2 first) { "userGroupV2": { "id": "group_id", @@ -519,6 +557,8 @@ def test_create_without_members_should_work(self, group_project): }, } }, + # Mock get_roles query response (even though no members, _get_members_set is still called) + {"roles": []}, ] group.create() @@ -588,7 +628,7 @@ def test_create_mutation(): } } }, - # Second call: get query + # Second call: get query (get() executes userGroupV2 first) { "userGroupV2": { "id": "group_id", @@ -599,6 +639,8 @@ def test_create_mutation(): "members": {"nodes": [], "totalCount": 0, "userGroupRoles": []}, } }, + # Third call: get_roles query (called by _get_members_set) + {"roles": []}, ] group.create() @@ -646,7 +688,7 @@ def test_update_mutation(): } } }, - # Second call: get query + # Second call: get query (get() executes userGroupV2 first) { "userGroupV2": { "id": "group_id", @@ -657,6 +699,8 @@ def test_update_mutation(): "members": {"nodes": [], "totalCount": 0, "userGroupRoles": []}, } }, + # Third call: get_roles query (called by _get_members_set) + {"roles": []}, ] group.update() From 3866a9790fb9f441b5a1584c18c1a162ffe7158f Mon Sep 17 00:00:00 2001 From: paulnoirel <87332996+paulnoirel@users.noreply.github.com> Date: Wed, 2 Jul 2025 23:48:14 +0100 Subject: [PATCH 2/5] Prevent workspace-wide users from being user group members --- .../src/labelbox/schema/user_group.py | 108 ++++++++------- .../tests/unit/schema/test_user_group.py | 25 +--- test_usergroup_fix.py | 126 ++++++++++++++++++ 3 files changed, 183 insertions(+), 76 deletions(-) create mode 100644 test_usergroup_fix.py diff --git a/libs/labelbox/src/labelbox/schema/user_group.py b/libs/labelbox/src/labelbox/schema/user_group.py index 1956a9f52..07e7ab6a7 100644 --- a/libs/labelbox/src/labelbox/schema/user_group.py +++ b/libs/labelbox/src/labelbox/schema/user_group.py @@ -230,7 +230,7 @@ def update(self) -> UserGroup: Raises: ValueError: If group ID or name is not set, or if projects don't exist. ResourceNotFoundError: If the group or projects are not found. - UnprocessableEntityError: If user validation fails. + UnprocessableEntityError: If user validation fails or users have workspace-level org roles. """ if not self.id: raise ValueError("Group id is required") @@ -247,8 +247,11 @@ def update(self) -> UserGroup: ) # Filter eligible users and build user roles - eligible_users = self._filter_project_based_users() - user_roles = self._build_user_roles(eligible_users) + try: + eligible_users = self._filter_project_based_users() + user_roles = self._build_user_roles(eligible_users) + except ValueError as e: + raise UnprocessableEntityError(str(e)) from e query = """ mutation UpdateUserGroupPyApi($id: ID!, $name: String!, $description: String, $color: String!, $projectIds: [ID!]!, $userRoles: [UserRoleInput!], $notifyMembers: Boolean) { @@ -312,7 +315,7 @@ def create(self) -> UserGroup: Raises: ValueError: If group already has ID, name is invalid, or projects don't exist. - ResourceCreationError: If creation fails or user validation fails. + ResourceCreationError: If creation fails, user validation fails, or users have workspace-level org roles. ResourceConflict: If a group with the same name already exists. """ if self.id: @@ -330,8 +333,11 @@ def create(self) -> UserGroup: ) # Filter eligible users and build user roles - eligible_users = self._filter_project_based_users() - user_roles = self._build_user_roles(eligible_users) + try: + eligible_users = self._filter_project_based_users() + user_roles = self._build_user_roles(eligible_users) + except ValueError as e: + raise ResourceCreationError(str(e)) from e query = """ mutation CreateUserGroupPyApi($name: String!, $description: String, $color: String!, $projectIds: [ID!]!, $userRoles: [UserRoleInput!]!, $notifyMembers: Boolean, $roleId: String, $searchQuery: AlignerrSearchServiceQuery) { @@ -496,11 +502,14 @@ def get_user_groups( def _filter_project_based_users(self) -> Set[User]: """Filter users to only include users eligible for UserGroups. - Filters out users with specific admin organization roles that cannot be - added to UserGroups. Most users should be eligible. + Only project-based users (org role "NONE") can be added to UserGroups. + Users with any workspace-level organization role cannot be added. Returns: Set of users that are eligible to be added to the group. + + Raises: + ValueError: If any user has a workspace-level organization role. """ all_users = set() for member in self.members: @@ -509,63 +518,52 @@ def _filter_project_based_users(self) -> Set[User]: if not all_users: return set() - user_ids = [user.uid for user in all_users] - query = """ - query CheckUserOrgRolesPyApi($userIds: [ID!]!) { - users(where: {id_in: $userIds}) { - id - orgRole { id name } - } - } - """ + # Check each user's org role directly + invalid_users = [] + eligible_users = set() - try: - result = self.client.execute(query, {"userIds": user_ids}) - if not result or "users" not in result: - return all_users # Fallback: let server handle validation - - # Check for users with org roles that cannot be used in UserGroups - # Only users with no org role (project-based users) can be assigned to UserGroups - eligible_user_ids = set() - invalid_users = [] - - for user_data in result["users"]: - org_role = user_data.get("orgRole") - user_id = user_data["id"] - user_email = user_data.get("email", "unknown") - - if org_role is None: - # Users with no org role (project-based users) are eligible - eligible_user_ids.add(user_id) + for user in all_users: + try: + # Get the user's organization role directly + org_role = user.org_role() + if org_role is None or org_role.name.upper() == "NONE": + # Users with no org role or "NONE" role are project-based and eligible + eligible_users.add(user) else: - # Users with ANY workspace org role cannot be assigned to UserGroups + # Users with any workspace org role cannot be assigned to UserGroups invalid_users.append( { - "id": user_id, - "email": user_email, - "org_role": org_role.get("name"), + "id": user.uid, + "email": getattr(user, "email", "unknown"), + "org_role": org_role.name, } ) + except Exception as e: + # If we can't determine the user's role, treat as invalid for safety + invalid_users.append( + { + "id": user.uid, + "email": getattr(user, "email", "unknown"), + "org_role": f"unknown (error: {str(e)})", + } + ) - # Raise error if any invalid users found - if invalid_users: - error_details = [] - for user in invalid_users: - error_details.append( - f"User {user['id']} ({user['email']}) has org role '{user['org_role']}'" - ) - - raise ValueError( - f"Cannot create UserGroup with users who have organization roles. " - f"Only project-based users (no org role) can be assigned to UserGroups.\n" - f"Invalid users:\n" - + "\n".join(f" โ€ข {detail}" for detail in error_details) + # Raise error if any invalid users found + if invalid_users: + error_details = [] + for user in invalid_users: + error_details.append( + f"User {user['id']} ({user['email']}) has org role '{user['org_role']}'" ) - return {user for user in all_users if user.uid in eligible_user_ids} + raise ValueError( + f"Cannot create UserGroup with users who have organization roles. " + f"Only project-based users (no org role or role 'NONE') can be assigned to UserGroups.\n" + f"Invalid users:\n" + + "\n".join(f" โ€ข {detail}" for detail in error_details) + ) - except Exception: - return all_users # Fallback: let server handle validation + return eligible_users def _build_user_roles( self, eligible_users: Set[User] diff --git a/libs/labelbox/tests/unit/schema/test_user_group.py b/libs/labelbox/tests/unit/schema/test_user_group.py index 5492f0a2c..c51e60c6a 100644 --- a/libs/labelbox/tests/unit/schema/test_user_group.py +++ b/libs/labelbox/tests/unit/schema/test_user_group.py @@ -41,7 +41,10 @@ def group_user(): user_values["createdAt"] = "2023-01-01T00:00:00Z" user_values["isExternalUser"] = False user_values["isViewer"] = False - return User(MagicMock(Client), user_values) + user = User(MagicMock(Client), user_values) + # Mock org_role() to return None (project-based user) + user.org_role = MagicMock(return_value=None) + return user @pytest.fixture @@ -237,16 +240,6 @@ def test_update(self, group_user, group_project, mock_role): self.client.get_project.return_value = group_project self.client.execute.side_effect = [ - # Mock user roles query response - { - "users": [ - { - "id": "user_id", - "email": "test@example.com", - "orgRole": None, # Project-based user - } - ] - }, # Mock update mutation response { "updateUserGroupV3": { @@ -458,16 +451,6 @@ def test_create(self, group_user, group_project, mock_role): self.client.get_project.return_value = group_project self.client.execute.side_effect = [ - # Mock user roles query response - { - "users": [ - { - "id": "user_id", - "email": "test@example.com", - "orgRole": None, # Project-based user - } - ] - }, # Mock create mutation response { "createUserGroupV3": { diff --git a/test_usergroup_fix.py b/test_usergroup_fix.py new file mode 100644 index 000000000..4b2002ef9 --- /dev/null +++ b/test_usergroup_fix.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +""" +Test script to demonstrate the fixed UserGroup validation behavior. +This script shows how workspace-based users are now properly rejected. +""" + +from unittest.mock import MagicMock, Mock +from collections import defaultdict + +# Import the fixed UserGroup classes +from libs.labelbox.src.labelbox.schema.user_group import UserGroup, UserGroupMember, UserGroupColor +from libs.labelbox.src.labelbox.schema.user import User +from libs.labelbox.src.labelbox.schema.role import Role +from lbox.exceptions import ResourceCreationError, UnprocessableEntityError + + +def create_mock_user(user_id: str, email: str, org_role_name: str = None): + """Create a mock user with specified org role.""" + client = MagicMock() + user_values = defaultdict(lambda: None) + user_values["id"] = user_id + user_values["email"] = email + + user = User(client, user_values) + + # Mock the org_role() method + if org_role_name is None or org_role_name.upper() == "NONE": + user.org_role = Mock(return_value=Mock(name="NONE")) + else: + user.org_role = Mock(return_value=Mock(name=org_role_name)) + + return user + + +def create_mock_role(role_name: str): + """Create a mock role.""" + client = MagicMock() + role_values = defaultdict(lambda: None) + role_values["id"] = f"role_{role_name.lower()}" + role_values["name"] = role_name + return Role(client, role_values) + + +def test_workspace_user_rejection(): + """Test that workspace-based users are properly rejected.""" + print("๐Ÿงช Testing UserGroup validation fixes...") + + client = MagicMock() + + # Create test users + project_user = create_mock_user("user1", "project@example.com", "NONE") # Valid + workspace_labeler = create_mock_user("user2", "labeler@example.com", "LABELER") # Invalid + workspace_admin = create_mock_user("user3", "admin@example.com", "ADMIN") # Invalid + + # Create test role + labeler_role = create_mock_role("LABELER") + + print("\nโœ… Test 1: Project-based user should be accepted") + try: + user_group = UserGroup( + client=client, + name="Valid Group", + color=UserGroupColor.BLUE, + members={UserGroupMember(user=project_user, role=labeler_role)} + ) + # This should work - call the validation method directly + eligible_users = user_group._filter_project_based_users() + print(f" โœ“ Project-based user accepted: {len(eligible_users)} eligible users") + except Exception as e: + print(f" โŒ Unexpected error: {e}") + + print("\nโŒ Test 2: Workspace labeler should be rejected") + try: + user_group = UserGroup( + client=client, + name="Invalid Group", + color=UserGroupColor.BLUE, + members={UserGroupMember(user=workspace_labeler, role=labeler_role)} + ) + # This should fail + eligible_users = user_group._filter_project_based_users() + print(f" โŒ ERROR: Workspace labeler was incorrectly accepted!") + except ValueError as e: + print(f" โœ“ Workspace labeler correctly rejected: {str(e)[:100]}...") + + print("\nโŒ Test 3: Workspace admin should be rejected") + try: + user_group = UserGroup( + client=client, + name="Invalid Group 2", + color=UserGroupColor.BLUE, + members={UserGroupMember(user=workspace_admin, role=labeler_role)} + ) + # This should fail + eligible_users = user_group._filter_project_based_users() + print(f" โŒ ERROR: Workspace admin was incorrectly accepted!") + except ValueError as e: + print(f" โœ“ Workspace admin correctly rejected: {str(e)[:100]}...") + + print("\nโŒ Test 4: Mixed users - should reject all if any are invalid") + try: + user_group = UserGroup( + client=client, + name="Mixed Group", + color=UserGroupColor.BLUE, + members={ + UserGroupMember(user=project_user, role=labeler_role), # Valid + UserGroupMember(user=workspace_labeler, role=labeler_role), # Invalid + } + ) + # This should fail because of the workspace labeler + eligible_users = user_group._filter_project_based_users() + print(f" โŒ ERROR: Mixed group with workspace user was incorrectly accepted!") + except ValueError as e: + print(f" โœ“ Mixed group correctly rejected: {str(e)[:100]}...") + + print("\n๐ŸŽ‰ All tests completed!") + print("\n๐Ÿ“‹ Summary:") + print(" โ€ข Project-based users (org role 'NONE' or null) โœ… ACCEPTED") + print(" โ€ข Workspace-based users (any org role) โŒ REJECTED") + print(" โ€ข Clear error messages provided") + print(" โ€ข No more silent failures or server crashes") + + +if __name__ == "__main__": + test_workspace_user_rejection() \ No newline at end of file From ef172e068f8817ee40383e6e6cd0d1ded4d4f154 Mon Sep 17 00:00:00 2001 From: paulnoirel <87332996+paulnoirel@users.noreply.github.com> Date: Wed, 2 Jul 2025 23:52:32 +0100 Subject: [PATCH 3/5] fix lint error --- libs/labelbox/src/labelbox/schema/user_group.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/labelbox/src/labelbox/schema/user_group.py b/libs/labelbox/src/labelbox/schema/user_group.py index 07e7ab6a7..e247af1c7 100644 --- a/libs/labelbox/src/labelbox/schema/user_group.py +++ b/libs/labelbox/src/labelbox/schema/user_group.py @@ -551,9 +551,9 @@ def _filter_project_based_users(self) -> Set[User]: # Raise error if any invalid users found if invalid_users: error_details = [] - for user in invalid_users: + for user_info in invalid_users: error_details.append( - f"User {user['id']} ({user['email']}) has org role '{user['org_role']}'" + f"User {user_info['id']} ({user_info['email']}) has org role '{user_info['org_role']}'" ) raise ValueError( From 7c11782ef5d79faba87603cc6521d01fab38b88a Mon Sep 17 00:00:00 2001 From: paulnoirel <87332996+paulnoirel@users.noreply.github.com> Date: Thu, 3 Jul 2025 00:14:20 +0100 Subject: [PATCH 4/5] Make tests for create-dataset more robust --- libs/labelbox/tests/conftest.py | 60 ++++++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 5 deletions(-) diff --git a/libs/labelbox/tests/conftest.py b/libs/labelbox/tests/conftest.py index a07d52c4d..2ef516ac2 100644 --- a/libs/labelbox/tests/conftest.py +++ b/libs/labelbox/tests/conftest.py @@ -501,14 +501,34 @@ def consensus_project_with_batch( @pytest.fixture def dataset(client, rand_gen): - dataset = client.create_dataset(name=rand_gen(str)) + # Handle invalid default IAM integrations in test environments gracefully + try: + dataset = client.create_dataset(name=rand_gen(str)) + except ValueError as e: + if "Integration is not valid" in str(e): + # Fallback to creating dataset without IAM integration for tests + dataset = client.create_dataset( + name=rand_gen(str), iam_integration=None + ) + else: + raise e yield dataset dataset.delete() @pytest.fixture(scope="function") def unique_dataset(client, rand_gen): - dataset = client.create_dataset(name=rand_gen(str)) + # Handle invalid default IAM integrations in test environments gracefully + try: + dataset = client.create_dataset(name=rand_gen(str)) + except ValueError as e: + if "Integration is not valid" in str(e): + # Fallback to creating dataset without IAM integration for tests + dataset = client.create_dataset( + name=rand_gen(str), iam_integration=None + ) + else: + raise e yield dataset dataset.delete() @@ -857,7 +877,17 @@ def func(project): @pytest.fixture def initial_dataset(client, rand_gen): - dataset = client.create_dataset(name=rand_gen(str)) + # Handle invalid default IAM integrations in test environments gracefully + try: + dataset = client.create_dataset(name=rand_gen(str)) + except ValueError as e: + if "Integration is not valid" in str(e): + # Fallback to creating dataset without IAM integration for tests + dataset = client.create_dataset( + name=rand_gen(str), iam_integration=None + ) + else: + raise e yield dataset dataset.delete() @@ -865,7 +895,17 @@ def initial_dataset(client, rand_gen): @pytest.fixture def video_data(client, rand_gen, video_data_row, wait_for_data_row_processing): - dataset = client.create_dataset(name=rand_gen(str)) + # Handle invalid default IAM integrations in test environments gracefully + try: + dataset = client.create_dataset(name=rand_gen(str)) + except ValueError as e: + if "Integration is not valid" in str(e): + # Fallback to creating dataset without IAM integration for tests + dataset = client.create_dataset( + name=rand_gen(str), iam_integration=None + ) + else: + raise e data_row_ids = [] data_row = dataset.create_data_row(video_data_row) data_row = wait_for_data_row_processing(client, data_row) @@ -884,7 +924,17 @@ def create_video_data_row(rand_gen): @pytest.fixture def video_data_100_rows(client, rand_gen, wait_for_data_row_processing): - dataset = client.create_dataset(name=rand_gen(str)) + # Handle invalid default IAM integrations in test environments gracefully + try: + dataset = client.create_dataset(name=rand_gen(str)) + except ValueError as e: + if "Integration is not valid" in str(e): + # Fallback to creating dataset without IAM integration for tests + dataset = client.create_dataset( + name=rand_gen(str), iam_integration=None + ) + else: + raise e data_row_ids = [] for _ in range(100): data_row = dataset.create_data_row(create_video_data_row(rand_gen)) From 092e889f562aeb8d8c991da6237a1040dd89beae Mon Sep 17 00:00:00 2001 From: paulnoirel <87332996+paulnoirel@users.noreply.github.com> Date: Thu, 3 Jul 2025 00:25:39 +0100 Subject: [PATCH 5/5] Fix test_get_slice --- libs/labelbox/tests/integration/test_slice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/labelbox/tests/integration/test_slice.py b/libs/labelbox/tests/integration/test_slice.py index 0521d24dd..a8db7f9ff 100644 --- a/libs/labelbox/tests/integration/test_slice.py +++ b/libs/labelbox/tests/integration/test_slice.py @@ -85,7 +85,7 @@ def test_get_slice(client): if s.name in ["Test Slice 1", "Test Slice 2"] ) for slice in slices: - _delete_catalog_slice(client, slice.id) + _delete_catalog_slice(client, slice.uid) # Create slices slice_id_1 = _create_catalog_slice(