diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 44b0db79..6d335aa0 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,6 +14,7 @@ Added - **Projectroles** - ``_project_badge.html`` template (#1300) - ``InvalidFormMixin`` helper mixin (#1310) + - Temporary ``user_name`` param in remote sync app settings (#1320) Changed ------- @@ -37,6 +38,7 @@ Fixed - Unhandled exceptions in ``validate_form_app_settings()`` calls (#1306) - ``validate_form_app_settings()`` results handling crash in ``ProjectForm`` (#1307) - ``RoleAssignment`` provided to ``validate_form_app_settings()`` in ``ProjectForm`` (#1308) + - ``PROJECT_USER`` app settings remote sync failure (#1315) - **Timeline** - ``get_timestamp()`` template tag crash from missing ``ProjectEventStatus`` (#1297) - **Userprofile** diff --git a/docs/source/major_changes.rst b/docs/source/major_changes.rst index ac6fc19d..2b0c8705 100644 --- a/docs/source/major_changes.rst +++ b/docs/source/major_changes.rst @@ -17,6 +17,7 @@ v0.13.3 (WIP) - Add InvalidFormMixin helper mixin - Fix hidden JSON project setting reset on non-superuser project update - Fix custom app setting validation calls in forms +- Fix remote sync PROJECT_USER app settings updating - General bug fixes and minor updates diff --git a/projectroles/remote_projects.py b/projectroles/remote_projects.py index e6b46577..1d7a57d2 100644 --- a/projectroles/remote_projects.py +++ b/projectroles/remote_projects.py @@ -184,6 +184,7 @@ def _add_app_setting(cls, sync_data, app_setting, all_defs): .get(app_setting.name, {}) .get('local', APP_SETTING_LOCAL_DEFAULT) ) + # TODO: Remove user_name once #1316 and #1317 are implemented sync_data['app_settings'][str(app_setting.sodar_uuid)] = { 'name': app_setting.name, 'type': app_setting.type, @@ -198,6 +199,9 @@ def _add_app_setting(cls, sync_data, app_setting, all_defs): 'user_uuid': app_setting.user.sodar_uuid if app_setting.user else None, + 'user_name': app_setting.user.username + if app_setting.user + else None, 'local': local, } return sync_data @@ -235,6 +239,7 @@ def get_source_data(self, target_site): ] # Get and add app settings for project + # NOTE: Default setting values are not synced for a in AppSetting.objects.filter(project=project): try: sync_data = self._add_app_setting(sync_data, a, all_defs) @@ -1044,8 +1049,22 @@ def _sync_app_setting(cls, uuid, set_data): if ad['project_uuid']: project = Project.objects.get(sodar_uuid=ad['project_uuid']) - if ad['user_uuid']: - user = User.objects.get(sodar_uuid=ad['user_uuid']) + # TODO: Use UUID for LDAP users once #1316 and #1317 are implemented + if ad.get('user_name'): + # User may not be found if e.g. local users allowed but not created + user = User.objects.filter(username=ad['user_name']).first() + if not user: + logger.info( + 'Skipping setting {}: User not found'.format(ad['name']) + ) + return + # Skip for now, as UUIDs have not been correctly synced + # TODO: Remove skip after #1316 and #1317 + elif ad['user_uuid']: + logger.info( + 'Skipping setting {}: user_name not present'.format(ad['name']) + ) + return try: obj = AppSetting.objects.get( @@ -1074,6 +1093,7 @@ def _sync_app_setting(cls, uuid, set_data): # Remove keys that are not available in the model ad.pop('local', None) ad.pop('project_uuid', None) + ad.pop('user_name', None) # TODO: Remove once user UUID support added ad.pop('user_uuid', None) # Add keys required for the model ad['project'] = project diff --git a/projectroles/tests/test_remote_projects_api.py b/projectroles/tests/test_remote_projects_api.py index 793e241d..ddf34103 100644 --- a/projectroles/tests/test_remote_projects_api.py +++ b/projectroles/tests/test_remote_projects_api.py @@ -124,24 +124,46 @@ NEW_PEER_DESC = PEER_SITE_DESC + ' new' NEW_PEER_USER_DISPLAY = not PEER_SITE_USER_DISPLAY -PR_IP_RESTRICT_UUID = str(uuid.uuid4()) -PR_IP_ALLOWLIST_UUID = str(uuid.uuid4()) +SET_IP_RESTRICT_UUID = str(uuid.uuid4()) +SET_IP_ALLOWLIST_UUID = str(uuid.uuid4()) +SET_STAR_UUID = str(uuid.uuid4()) + + +class RemoteProjectsAPITestBase(RoleMixin, TestCase): + """Base class for remote project API tests""" + + def assert_app_setting(self, uuid, expected): + """ + Assert app setting model data. Model id and sodar_uuid fields can be + left out of the expected dict, they will be populated automatically. + + :param uuid: AppSetting UUID + :param expected: Dict of expected data as model_to_dict output + """ + set_obj = AppSetting.objects.get(sodar_uuid=uuid) + expected['id'] = set_obj.id + expected['sodar_uuid'] = set_obj.sodar_uuid + set_dict = model_to_dict(set_obj) + self.assertEqual(set_dict, expected) + + def setUp(self): + # Init roles + self.init_roles() class TestGetSourceData( ProjectMixin, - RoleMixin, RoleAssignmentMixin, RemoteSiteMixin, RemoteProjectMixin, SODARUserMixin, - TestCase, + AppSettingMixin, + RemoteProjectsAPITestBase, ): - """Tests for the get_source_data() API function""" + """Tests for get_source_data()""" def setUp(self): - # Init roles - self.init_roles() + super().setUp() # Init an LDAP user on the source site self.user_source = self.make_sodar_user( username=SOURCE_USER_USERNAME, @@ -183,8 +205,8 @@ def setUp(self): ) self.remote_api = RemoteProjectAPI() - def test_view_avail(self): - """Test get data with project level of VIEW_AVAIL (view availability)""" + def test_get_view_avail(self): + """Test getting data with VIEW_AVAIL level""" self.make_remote_project( project_uuid=self.project.sodar_uuid, site=self.target_site, @@ -212,8 +234,8 @@ def test_view_avail(self): } self.assertEqual(sync_data, expected) - def test_read_info(self): - """Test get data with project level of READ_INFO""" + def test_get_read_info(self): + """Test getting data with READ_INFO level""" self.make_remote_project( project_uuid=self.project.sodar_uuid, site=self.target_site, @@ -258,8 +280,8 @@ def test_read_info(self): } self.assertEqual(sync_data, expected) - def test_read_info_nested(self): - """Test get data with READ_INFO and nested categories""" + def test_get_read_info_nested(self): + """Test getting data with READ_INFO and nested categories""" sub_category = self.make_project( 'SubCategory', PROJECT_TYPE_CATEGORY, parent=self.category ) @@ -306,8 +328,8 @@ def test_read_info_nested(self): } self.assertEqual(sync_data, expected) - def test_read_roles(self): - """Test get data with project level of READ_ROLES""" + def test_get_read_roles(self): + """Test getting data with READ_ROLES level""" self.make_remote_project( project_uuid=self.project.sodar_uuid, site=self.target_site, @@ -373,8 +395,8 @@ def test_read_roles(self): } self.assertEqual(sync_data, expected) - def test_read_roles_inherited(self): - """Test get data with READ_ROLES and inherited contributor""" + def test_get_read_roles_inherited(self): + """Test getting data with READ_ROLES and inherited contributor""" user_new_name = 'user_new@' + SOURCE_USER_DOMAIN user_new = self.make_sodar_user( username=user_new_name, @@ -411,8 +433,97 @@ def test_read_roles_inherited(self): PROJECT_ROLE_CONTRIBUTOR, ) - def test_revoked(self): - """Test get data with project level of REVOKED""" + def test_get_settings(self): + """Test getting data with app settings""" + self.make_remote_project( + project_uuid=self.project.sodar_uuid, + site=self.target_site, + level=REMOTE_LEVEL_READ_ROLES, + ) + # Init local project (settings should not be synced) + local_project = self.make_project( + 'LocalProject', PROJECT_TYPE_PROJECT, self.category + ) + self.make_assignment(local_project, self.user_source, self.role_owner) + # Init settings for synced project + set_ip_restrict = self.make_setting( + app_name='projectroles', + name='ip_restrict', + setting_type='BOOLEAN', + value=True, + project=self.project, + ) + set_ip_allowlist = self.make_setting( + app_name='projectroles', + name='ip_allowlist', + setting_type='JSON', + value=None, + value_json=['127.0.0.1'], + project=self.project, + ) + set_star = self.make_setting( + app_name='projectroles', + name='project_star', + setting_type='BOOLEAN', + value=True, + project=self.project, + user=self.user_source, + ) + # Init setting for local project (should not be synced) + set_local = self.make_setting( + app_name='projectroles', + name='project_star', + setting_type='BOOLEAN', + value=True, + project=local_project, + user=self.user_source, + ) + sync_data = self.remote_api.get_source_data(self.target_site) + + self.assertEqual(len(sync_data['app_settings']), 3) + self.assertNotIn(set_local, sync_data['app_settings']) + set_data = sync_data['app_settings'][str(set_ip_restrict.sodar_uuid)] + expected = { + 'name': set_ip_restrict.name, + 'type': set_ip_restrict.type, + 'value': set_ip_restrict.value, + 'value_json': {}, + 'app_plugin': None, + 'project_uuid': self.project.sodar_uuid, + 'user_uuid': None, + 'user_name': None, + 'local': False, + } + self.assertEqual(set_data, expected) + set_data = sync_data['app_settings'][str(set_ip_allowlist.sodar_uuid)] + expected = { + 'name': set_ip_allowlist.name, + 'type': set_ip_allowlist.type, + 'value': None, + 'value_json': set_ip_allowlist.value_json, + 'app_plugin': None, + 'project_uuid': self.project.sodar_uuid, + 'user_uuid': None, + 'user_name': None, + 'local': False, + } + self.assertEqual(set_data, expected) + set_data = sync_data['app_settings'][str(set_star.sodar_uuid)] + expected = { + 'name': set_star.name, + 'type': set_star.type, + 'value': '1', + 'value_json': {}, + 'app_plugin': None, + 'project_uuid': self.project.sodar_uuid, + 'user_uuid': self.user_source.sodar_uuid, + 'user_name': self.user_source.username, + 'local': False, + } + self.assertEqual(set_data, expected) + + def test_get_revoked(self): + """Test getting data with REVOKED level""" user_source_new = self.make_user('new_source_user') self.make_assignment(self.project, user_source_new, self.role_guest) self.make_remote_project( @@ -467,8 +578,8 @@ def test_revoked(self): } self.assertEqual(sync_data, expected) - def test_no_access(self): - """Test get data with no project access set in the source site""" + def test_get_no_access(self): + """Test getting data with no project access set in source site""" sync_data = self.remote_api.get_source_data(self.target_site) expected = { 'users': {}, @@ -480,26 +591,24 @@ def test_no_access(self): @override_settings(PROJECTROLES_SITE_MODE=SITE_MODE_TARGET) -class TestSyncRemoteDataBase( +class SyncRemoteDataTestBase( ProjectMixin, - RoleMixin, RoleAssignmentMixin, RemoteSiteMixin, RemoteProjectMixin, SODARUserMixin, AppSettingMixin, - TestCase, + RemoteProjectsAPITestBase, ): - """Base class for tests for the sync_remote_data() API function""" + """Base class for tests for sync_remote_data()""" def setUp(self): + super().setUp() # Init users self.admin_user = self.make_user(settings.PROJECTROLES_DEFAULT_ADMIN) self.admin_user.is_staff = True self.admin_user.is_superuser = True self.maxDiff = None - # Init roles - self.init_roles() # Init source site self.source_site = self.make_site( name=SOURCE_SITE_NAME, @@ -563,7 +672,7 @@ def setUp(self): } }, 'app_settings': { - PR_IP_RESTRICT_UUID: { + SET_IP_RESTRICT_UUID: { 'name': 'ip_restrict', 'type': 'BOOLEAN', 'value': False, @@ -573,7 +682,7 @@ def setUp(self): 'user_uuid': None, 'local': False, }, - PR_IP_ALLOWLIST_UUID: { + SET_IP_ALLOWLIST_UUID: { 'name': 'ip_allowlist', 'type': 'JSON', 'value': '', @@ -583,13 +692,24 @@ def setUp(self): 'user_uuid': None, 'local': False, }, + SET_STAR_UUID: { + 'name': 'project_star', + 'type': 'BOOLEAN', + 'value': '1', + 'value_json': {}, + 'app_plugin': None, + 'project_uuid': SOURCE_PROJECT_UUID, + 'user_uuid': SOURCE_USER_UUID, + 'user_name': SOURCE_USER_USERNAME, + 'local': False, + }, }, } @override_settings(PROJECTROLES_SITE_MODE=SITE_MODE_TARGET) -class TestSyncRemoteDataCreate(TestSyncRemoteDataBase): - """Creation tests for the sync_remote_data() API function""" +class TestSyncRemoteDataCreate(SyncRemoteDataTestBase): + """Tests for project creation with sync_remote_data()""" def test_create(self): """Test sync with non-existing project data and READ_ROLE access""" @@ -662,7 +782,7 @@ def test_create(self): }, } ) - original_data = deepcopy(self.default_data) + og_data = deepcopy(self.default_data) # Do sync self.remote_api.sync_remote_data(self.source_site, self.default_data) @@ -672,7 +792,7 @@ def test_create(self): self.assertEqual(User.objects.all().count(), 5) self.assertEqual(RemoteProject.objects.all().count(), 3) self.assertEqual(RemoteSite.objects.all().count(), 2) - self.assertEqual(AppSetting.objects.count(), 2) + self.assertEqual(AppSetting.objects.count(), 3) new_user = User.objects.get(username=SOURCE_USER_USERNAME) new_user2 = User.objects.get(username=SOURCE_USER2_USERNAME) @@ -861,49 +981,43 @@ def test_create(self): peer_project_dict.pop('date_access') self.assertEqual(peer_project_dict, expected) - app_setting_ip_restrict_obj = AppSetting.objects.get( - sodar_uuid=PR_IP_RESTRICT_UUID, - ) - app_setting_ip_allowlist_obj = AppSetting.objects.get( - sodar_uuid=PR_IP_ALLOWLIST_UUID, - ) - - expected_ip_restrict = { + # Assert app settings + expected = { 'name': 'ip_restrict', 'type': 'BOOLEAN', 'value': '0', 'value_json': {}, - 'sodar_uuid': uuid.UUID(PR_IP_RESTRICT_UUID), 'project': project_obj.id, 'app_plugin': None, 'user': None, 'user_modifiable': True, } - expected_ip_allowlist = { + self.assert_app_setting(SET_IP_RESTRICT_UUID, expected) + expected = { 'name': 'ip_allowlist', 'type': 'JSON', 'value': '', 'value_json': [], - 'sodar_uuid': uuid.UUID(PR_IP_ALLOWLIST_UUID), 'project': project_obj.id, 'app_plugin': None, 'user': None, 'user_modifiable': True, } - app_setting_ip_restrict_dict = model_to_dict( - app_setting_ip_restrict_obj - ) - app_setting_ip_allowlist_dict = model_to_dict( - app_setting_ip_allowlist_obj - ) - app_setting_ip_restrict_dict.pop('id') - app_setting_ip_allowlist_dict.pop('id') - - self.assertEqual(app_setting_ip_allowlist_dict, expected_ip_allowlist) - self.assertEqual(app_setting_ip_restrict_dict, expected_ip_restrict) + self.assert_app_setting(SET_IP_ALLOWLIST_UUID, expected) + expected = { + 'name': 'project_star', + 'type': 'BOOLEAN', + 'value': '1', + 'value_json': {}, + 'app_plugin': None, + 'project': project_obj.id, + 'user': new_user.id, + 'user_modifiable': True, + } + self.assert_app_setting(SET_STAR_UUID, expected) # Assert remote_data changes - expected = original_data + expected = og_data expected['users'][SOURCE_USER_UUID]['status'] = 'created' expected['users'][SOURCE_USER2_UUID]['status'] = 'created' expected['users'][SOURCE_USER3_UUID]['status'] = 'created' @@ -934,19 +1048,20 @@ def test_create(self): expected['projects'][SOURCE_PROJECT_UUID]['roles'][ SOURCE_PROJECT_ROLE4_UUID ]['status'] = 'created' - expected['app_settings'][PR_IP_RESTRICT_UUID]['status'] = 'created' - expected['app_settings'][PR_IP_ALLOWLIST_UUID]['status'] = 'created' + expected['app_settings'][SET_IP_RESTRICT_UUID]['status'] = 'created' + expected['app_settings'][SET_IP_ALLOWLIST_UUID]['status'] = 'created' + expected['app_settings'][SET_STAR_UUID]['status'] = 'created' self.assertEqual(self.default_data, expected) + # TODO: Update once local settings updating is fixed def test_create_app_setting_local(self): - """Test creating a local app setting""" + """Test sync with local app setting""" remote_data = self.default_data - remote_data['app_settings'][PR_IP_RESTRICT_UUID]['local'] = True - remote_data['app_settings'][PR_IP_ALLOWLIST_UUID]['local'] = True - original_data = deepcopy(remote_data) + remote_data['app_settings'][SET_IP_RESTRICT_UUID]['local'] = True + remote_data['app_settings'][SET_IP_ALLOWLIST_UUID]['local'] = True + expected = deepcopy(remote_data) self.remote_api.sync_remote_data(self.source_site, remote_data) - expected = original_data expected['users'][SOURCE_USER_UUID]['status'] = 'created' expected['projects'][SOURCE_CATEGORY_UUID]['status'] = 'created' expected['projects'][SOURCE_CATEGORY_UUID]['roles'][ @@ -956,12 +1071,13 @@ def test_create_app_setting_local(self): expected['projects'][SOURCE_PROJECT_UUID]['roles'][ SOURCE_PROJECT_ROLE_UUID ]['status'] = 'created' - expected['app_settings'][PR_IP_RESTRICT_UUID]['status'] = 'created' - expected['app_settings'][PR_IP_ALLOWLIST_UUID]['status'] = 'created' + expected['app_settings'][SET_IP_RESTRICT_UUID]['status'] = 'created' + expected['app_settings'][SET_IP_ALLOWLIST_UUID]['status'] = 'created' + expected['app_settings'][SET_STAR_UUID]['status'] = 'created' self.assertEqual(remote_data, expected) def test_create_multiple(self): - """Test sync with non-existing project data and multiple projects""" + """Test sync with multiple non-existing projects""" self.assertEqual(Project.objects.all().count(), 0) self.assertEqual(RoleAssignment.objects.all().count(), 0) self.assertEqual(User.objects.all().count(), 1) @@ -986,7 +1102,7 @@ def test_create_multiple(self): } }, } - original_data = deepcopy(remote_data) + og_data = deepcopy(remote_data) self.remote_api.sync_remote_data(self.source_site, remote_data) self.assertEqual(Project.objects.all().count(), 3) @@ -1024,7 +1140,7 @@ def test_create_multiple(self): } self.assertEqual(model_to_dict(p_new_owner_obj), expected) - expected = original_data + expected = og_data expected['users'][SOURCE_USER_UUID]['status'] = 'created' expected['projects'][SOURCE_CATEGORY_UUID]['status'] = 'created' expected['projects'][SOURCE_CATEGORY_UUID]['roles'][ @@ -1038,12 +1154,13 @@ def test_create_multiple(self): expected['projects'][new_project_uuid]['roles'][new_role_uuid][ 'status' ] = 'created' - expected['app_settings'][PR_IP_RESTRICT_UUID]['status'] = 'created' - expected['app_settings'][PR_IP_ALLOWLIST_UUID]['status'] = 'created' + expected['app_settings'][SET_IP_RESTRICT_UUID]['status'] = 'created' + expected['app_settings'][SET_IP_ALLOWLIST_UUID]['status'] = 'created' + expected['app_settings'][SET_STAR_UUID]['status'] = 'created' self.assertEqual(remote_data, expected) def test_create_local_owner(self): - """Test sync with non-existing project data and a local owner""" + """Test sync with non-existing project data and local owner""" self.assertEqual(Project.objects.all().count(), 0) self.assertEqual(RoleAssignment.objects.all().count(), 0) self.assertEqual(User.objects.all().count(), 1) @@ -1110,10 +1227,7 @@ def test_create_inherited(self): } remote_data['projects'][SOURCE_CATEGORY_UUID]['roles'][ new_role_uuid - ] = { - 'user': new_user_username, - 'role': PROJECT_ROLE_CONTRIBUTOR, - } + ] = {'user': new_user_username, 'role': PROJECT_ROLE_CONTRIBUTOR} self.remote_api.sync_remote_data(self.source_site, remote_data) self.assertEqual(User.objects.all().count(), 3) @@ -1133,7 +1247,7 @@ def test_create_no_access(self): remote_data['projects'][SOURCE_PROJECT_UUID][ 'level' ] = REMOTE_LEVEL_READ_INFO - original_data = deepcopy(remote_data) + og_data = deepcopy(remote_data) self.remote_api.sync_remote_data(self.source_site, remote_data) self.assertEqual(Project.objects.all().count(), 0) @@ -1142,10 +1256,10 @@ def test_create_no_access(self): self.assertEqual(RemoteProject.objects.all().count(), 0) self.assertEqual(RemoteSite.objects.all().count(), 1) # Assert no changes between update_data and remote_data - self.assertEqual(original_data, remote_data) + self.assertEqual(og_data, remote_data) def test_create_local_user(self): - """Test sync with a local non-owner user""" + """Test sync with local non-owner user""" local_user_username = 'localusername' local_user_uuid = str(uuid.uuid4()) role_uuid = str(uuid.uuid4()) @@ -1175,12 +1289,12 @@ def test_create_local_user(self): @override_settings(PROJECTROLES_SITE_MODE=SITE_MODE_TARGET) @override_settings(PROJECTROLES_ALLOW_LOCAL_USERS=True) def test_create_local_user_allow(self): - """Test sync with a local user with local users allowed""" + """Test sync with local user with local users allowed""" local_user_username = 'localusername' local_user_uuid = str(uuid.uuid4()) role_uuid = str(uuid.uuid4()) remote_data = self.default_data - # Create the user on the target site + # Create user on target site self.make_user(local_user_username) remote_data['users'][local_user_uuid] = { 'sodar_uuid': local_user_uuid, @@ -1205,7 +1319,7 @@ def test_create_local_user_allow(self): @override_settings(PROJECTROLES_ALLOW_LOCAL_USERS=True) def test_create_local_user_allow_unavailable(self): - """Test sync with a non-existent local user with local users allowed""" + """Test sync with non-existent local user and local users allowed""" local_user_username = 'localusername' local_user_uuid = str(uuid.uuid4()) role_uuid = str(uuid.uuid4()) @@ -1233,7 +1347,7 @@ def test_create_local_user_allow_unavailable(self): @override_settings(PROJECTROLES_ALLOW_LOCAL_USERS=True) def test_create_local_owner_allow(self): - """Test sync with a local owner with local users allowed""" + """Test sync with local owner and local users allowed""" local_user_username = 'localusername' local_user_uuid = str(uuid.uuid4()) role_uuid = str(uuid.uuid4()) @@ -1268,7 +1382,7 @@ def test_create_local_owner_allow(self): @override_settings(PROJECTROLES_ALLOW_LOCAL_USERS=True) def test_create_local_owner_allow_unavailable(self): - """Test sync with an unavailable local owner""" + """Test sync with unavailable local owner""" local_user_username = 'localusername' local_user_uuid = str(uuid.uuid4()) role_uuid = str(uuid.uuid4()) @@ -1296,18 +1410,16 @@ def test_create_local_owner_allow_unavailable(self): self.assertEqual(User.objects.all().count(), 2) self.assertEqual(RemoteProject.objects.all().count(), 3) self.assertEqual(RemoteSite.objects.all().count(), 2) - # Assert owner role new_project = Project.objects.get(sodar_uuid=SOURCE_PROJECT_UUID) self.assertEqual(new_project.get_owner().user, self.admin_user) @override_settings(PROJECTROLES_SITE_MODE=SITE_MODE_TARGET) -class TestSyncRemoteDataUpdate(TestSyncRemoteDataBase): - """Updating tests for the sync_remote_data() API function""" +class TestSyncRemoteDataUpdate(SyncRemoteDataTestBase): + """Tests for project updating with sync_remote_data()""" def setUp(self): super().setUp() - # Set up target category and project self.category_obj = self.make_project( title='NewCategoryTitle', @@ -1327,7 +1439,7 @@ def setUp(self): ) # Set up user and roles - self.target_user = self.make_sodar_user( + self.user_target = self.make_sodar_user( username=SOURCE_USER_USERNAME, name='NewFirstName NewLastName', first_name='NewFirstName', @@ -1335,10 +1447,10 @@ def setUp(self): email='newemail@example.com', ) self.c_owner_obj = self.make_assignment( - self.category_obj, self.target_user, self.role_owner + self.category_obj, self.user_target, self.role_owner ) self.p_owner_obj = self.make_assignment( - self.project_obj, self.target_user, self.role_owner + self.project_obj, self.user_target, self.role_owner ) # Set up RemoteProject objects @@ -1374,16 +1486,15 @@ def setUp(self): level=SODAR_CONSTANTS['REMOTE_LEVEL_NONE'], ) - # Init IP restrict setting + # Init app settings self.make_setting( app_name='projectroles', name='ip_restrict', setting_type='BOOLEAN', value=False, project=self.project_obj, - sodar_uuid=PR_IP_RESTRICT_UUID, + sodar_uuid=SET_IP_RESTRICT_UUID, ) - # Init IP allowlist setting self.make_setting( app_name='projectroles', name='ip_allowlist', @@ -1391,7 +1502,16 @@ def setUp(self): value=None, value_json=[], project=self.project_obj, - sodar_uuid=PR_IP_ALLOWLIST_UUID, + sodar_uuid=SET_IP_ALLOWLIST_UUID, + ) + self.make_setting( + app_name='projectroles', + name='project_star', + setting_type='BOOLEAN', + value=False, + project=self.project_obj, + user=self.user_target, + sodar_uuid=SET_STAR_UUID, ) # Update default data @@ -1408,7 +1528,7 @@ def test_update(self): self.assertEqual(User.objects.all().count(), 2) self.assertEqual(RemoteProject.objects.all().count(), 3) self.assertEqual(RemoteSite.objects.all().count(), 2) - self.assertEqual(AppSetting.objects.count(), 2) + self.assertEqual(AppSetting.objects.count(), 3) remote_data = self.default_data # Add new user and contributor role to source project @@ -1434,10 +1554,10 @@ def test_update(self): remote_data['peer_sites'][PEER_SITE_UUID][ 'user_display' ] = NEW_PEER_USER_DISPLAY - original_data = deepcopy(remote_data) + og_data = deepcopy(remote_data) # Change projectroles app settings - remote_data['app_settings'][PR_IP_RESTRICT_UUID]['value'] = True - remote_data['app_settings'][PR_IP_ALLOWLIST_UUID]['value_json'] = [ + remote_data['app_settings'][SET_IP_RESTRICT_UUID]['value'] = True + remote_data['app_settings'][SET_IP_ALLOWLIST_UUID]['value_json'] = [ '192.168.1.1' ] self.remote_api.sync_remote_data(self.source_site, remote_data) @@ -1447,7 +1567,7 @@ def test_update(self): self.assertEqual(User.objects.all().count(), 3) self.assertEqual(RemoteProject.objects.all().count(), 3) self.assertEqual(RemoteSite.objects.all().count(), 2) - self.assertEqual(AppSetting.objects.count(), 2) + self.assertEqual(AppSetting.objects.count(), 3) new_user = User.objects.get(username=new_user_username) self.category_obj.refresh_from_db() @@ -1471,7 +1591,7 @@ def test_update(self): expected = { 'id': self.c_owner_obj.pk, 'project': self.category_obj.pk, - 'user': self.target_user.pk, + 'user': self.user_target.pk, 'role': self.role_owner.pk, 'sodar_uuid': self.c_owner_obj.sodar_uuid, } @@ -1498,7 +1618,7 @@ def test_update(self): expected = { 'id': self.p_owner_obj.pk, 'project': self.project_obj.pk, - 'user': self.target_user.pk, + 'user': self.user_target.pk, 'role': self.role_owner.pk, 'sodar_uuid': self.p_owner_obj.sodar_uuid, } @@ -1574,48 +1694,43 @@ def test_update(self): peer_project_dict.pop('date_access') self.assertEqual(peer_project_dict, expected) - app_setting_ip_restrict_obj = AppSetting.objects.get( - sodar_uuid=PR_IP_RESTRICT_UUID, - ) - app_setting_ip_allowlist_obj = AppSetting.objects.get( - sodar_uuid=PR_IP_ALLOWLIST_UUID, - ) - expected_ip_restrict = { + # Assert app settings + expected = { 'name': 'ip_restrict', 'type': 'BOOLEAN', 'value': '1', 'value_json': {}, - 'sodar_uuid': uuid.UUID(PR_IP_RESTRICT_UUID), 'project': self.project_obj.id, 'app_plugin': None, 'user': None, 'user_modifiable': True, } - expected_ip_allowlist = { + self.assert_app_setting(SET_IP_RESTRICT_UUID, expected) + expected = { 'name': 'ip_allowlist', 'type': 'JSON', 'value': '', 'value_json': ['192.168.1.1'], - 'sodar_uuid': uuid.UUID(PR_IP_ALLOWLIST_UUID), 'project': self.project_obj.id, 'app_plugin': None, 'user': None, 'user_modifiable': True, } - app_setting_ip_restrict_dict = model_to_dict( - app_setting_ip_restrict_obj - ) - app_setting_ip_allowlist_dict = model_to_dict( - app_setting_ip_allowlist_obj - ) - app_setting_ip_restrict_dict.pop('id') - app_setting_ip_allowlist_dict.pop('id') - - self.assertEqual(app_setting_ip_allowlist_dict, expected_ip_allowlist) - self.assertEqual(app_setting_ip_restrict_dict, expected_ip_restrict) + self.assert_app_setting(SET_IP_ALLOWLIST_UUID, expected) + expected = { + 'name': 'project_star', + 'type': 'BOOLEAN', + 'value': '1', + 'value_json': {}, + 'app_plugin': None, + 'project': self.project_obj.id, + 'user': self.user_target.id, + 'user_modifiable': True, + } + self.assert_app_setting(SET_STAR_UUID, expected) # Assert update_data changes - expected = original_data + expected = og_data expected['users'][SOURCE_USER_UUID]['status'] = 'updated' expected['users'][new_user_uuid]['status'] = 'created' expected['projects'][SOURCE_CATEGORY_UUID]['status'] = 'updated' @@ -1623,12 +1738,13 @@ def test_update(self): expected['projects'][SOURCE_PROJECT_UUID]['roles'][new_role_uuid][ 'status' ] = 'created' - expected['app_settings'][PR_IP_RESTRICT_UUID]['value'] = True - expected['app_settings'][PR_IP_ALLOWLIST_UUID]['value_json'] = [ + expected['app_settings'][SET_IP_RESTRICT_UUID]['value'] = True + expected['app_settings'][SET_IP_ALLOWLIST_UUID]['value_json'] = [ '192.168.1.1' ] - expected['app_settings'][PR_IP_RESTRICT_UUID]['status'] = 'updated' - expected['app_settings'][PR_IP_ALLOWLIST_UUID]['status'] = 'updated' + expected['app_settings'][SET_IP_RESTRICT_UUID]['status'] = 'updated' + expected['app_settings'][SET_IP_ALLOWLIST_UUID]['status'] = 'updated' + expected['app_settings'][SET_STAR_UUID]['status'] = 'updated' self.assertEqual(remote_data, expected) def test_update_inherited(self): @@ -1649,10 +1765,7 @@ def test_update_inherited(self): } remote_data['projects'][SOURCE_CATEGORY_UUID]['roles'][ new_role_uuid - ] = { - 'user': new_user_username, - 'role': PROJECT_ROLE_CONTRIBUTOR, - } + ] = {'user': new_user_username, 'role': PROJECT_ROLE_CONTRIBUTOR} self.remote_api.sync_remote_data(self.source_site, remote_data) self.assertEqual(User.objects.all().count(), 3) @@ -1664,68 +1777,65 @@ def test_update_inherited(self): self.role_contributor, ) + # TODO: Update once local settings updating is fixed def test_update_app_setting_local(self): - """Test update with a local app setting""" + """Test update with local app settings""" remote_data = self.default_data # Change projectroles app settings - remote_data['app_settings'][PR_IP_RESTRICT_UUID]['local'] = True - remote_data['app_settings'][PR_IP_ALLOWLIST_UUID]['local'] = True - remote_data['app_settings'][PR_IP_RESTRICT_UUID]['value'] = True - remote_data['app_settings'][PR_IP_ALLOWLIST_UUID]['value_json'] = [ + remote_data['app_settings'][SET_IP_RESTRICT_UUID]['local'] = True + remote_data['app_settings'][SET_IP_ALLOWLIST_UUID]['local'] = True + remote_data['app_settings'][SET_IP_RESTRICT_UUID]['value'] = True + remote_data['app_settings'][SET_IP_ALLOWLIST_UUID]['value_json'] = [ '192.168.1.1' ] - original_data = deepcopy(remote_data) + og_data = deepcopy(remote_data) self.remote_api.sync_remote_data(self.source_site, remote_data) - app_setting_ip_restrict_obj = AppSetting.objects.get( - sodar_uuid=PR_IP_RESTRICT_UUID, - ) - app_setting_ip_allowlist_obj = AppSetting.objects.get( - sodar_uuid=PR_IP_ALLOWLIST_UUID, - ) - expected_ip_restrict = { + expected = { 'name': 'ip_restrict', 'type': 'BOOLEAN', 'value': '0', 'value_json': {}, - 'sodar_uuid': uuid.UUID(PR_IP_RESTRICT_UUID), 'project': self.project_obj.id, 'app_plugin': None, 'user': None, 'user_modifiable': True, } - expected_ip_allowlist = { + self.assert_app_setting(SET_IP_RESTRICT_UUID, expected) + expected = { 'name': 'ip_allowlist', 'type': 'JSON', 'value': None, 'value_json': [], - 'sodar_uuid': uuid.UUID(PR_IP_ALLOWLIST_UUID), 'project': self.project_obj.id, 'app_plugin': None, 'user': None, 'user_modifiable': True, } - app_setting_ip_restrict_dict = model_to_dict( - app_setting_ip_restrict_obj - ) - app_setting_ip_allowlist_dict = model_to_dict( - app_setting_ip_allowlist_obj - ) - app_setting_ip_restrict_dict.pop('id') - app_setting_ip_allowlist_dict.pop('id') - self.assertEqual(app_setting_ip_allowlist_dict, expected_ip_allowlist) - self.assertEqual(app_setting_ip_restrict_dict, expected_ip_restrict) + self.assert_app_setting(SET_IP_ALLOWLIST_UUID, expected) + expected = { + 'name': 'project_star', + 'type': 'BOOLEAN', + 'value': '1', + 'value_json': {}, + 'app_plugin': None, + 'project': self.project_obj.id, + 'user': self.user_target.id, + 'user_modifiable': True, + } + self.assert_app_setting(SET_STAR_UUID, expected) # Assert update_data changes - expected = original_data + expected = og_data expected['users'][SOURCE_USER_UUID]['status'] = 'updated' expected['projects'][SOURCE_CATEGORY_UUID]['status'] = 'updated' expected['projects'][SOURCE_PROJECT_UUID]['status'] = 'updated' + expected['app_settings'][SET_STAR_UUID]['status'] = 'updated' self.assertEqual(remote_data, expected) def test_update_app_setting_no_app(self): """Test update with app setting for app not present on target site""" - self.assertEqual(AppSetting.objects.count(), 2) + self.assertEqual(AppSetting.objects.count(), 3) remote_data = self.default_data setting_uuid = str(uuid.uuid4()) setting_name = 'NOT_A_VALID_SETTING' @@ -1778,14 +1888,12 @@ def test_update_revoke(self): self.assertEqual(RemoteSite.objects.all().count(), 2) new_user = User.objects.get(username=new_user_username) - # Assert removal of role assignment with self.assertRaises(RoleAssignment.DoesNotExist): RoleAssignment.objects.get( project__sodar_uuid=SOURCE_PROJECT_UUID, user=new_user, role__name=PROJECT_ROLE_CONTRIBUTOR, ) - # Assert update_data changes self.assertEqual( remote_data['projects'][SOURCE_PROJECT_UUID]['level'], REMOTE_LEVEL_REVOKED, @@ -1793,7 +1901,7 @@ def test_update_revoke(self): self.assertNotIn(str(new_user.sodar_uuid), remote_data['users'].keys()) def test_delete_role(self): - """Test sync with existing project data and a removed role""" + """Test sync with existing project data and removed role""" # Add new user and contributor role in target site new_user_username = 'newuser@' + SOURCE_USER_DOMAIN new_user = self.make_user(new_user_username) @@ -1809,7 +1917,7 @@ def test_delete_role(self): self.assertEqual(RemoteSite.objects.all().count(), 2) remote_data = self.default_data - original_data = deepcopy(remote_data) + og_data = deepcopy(remote_data) self.remote_api.sync_remote_data(self.source_site, remote_data) self.assertEqual(Project.objects.all().count(), 2) @@ -1823,14 +1931,15 @@ def test_delete_role(self): role__name=PROJECT_ROLE_CONTRIBUTOR, ) - expected = original_data + expected = og_data expected['projects'][SOURCE_PROJECT_UUID]['roles'][new_role_uuid] = { 'user': new_user_username, 'role': PROJECT_ROLE_CONTRIBUTOR, 'status': 'deleted', } - expected['app_settings'][PR_IP_RESTRICT_UUID]['status'] = 'updated' - expected['app_settings'][PR_IP_ALLOWLIST_UUID]['status'] = 'updated' + expected['app_settings'][SET_IP_RESTRICT_UUID]['status'] = 'updated' + expected['app_settings'][SET_IP_ALLOWLIST_UUID]['status'] = 'updated' + expected['app_settings'][SET_STAR_UUID]['status'] = 'updated' self.assertEqual(remote_data, expected) def test_update_no_changes(self): @@ -1842,7 +1951,7 @@ def test_update_no_changes(self): self.assertEqual(RemoteSite.objects.all().count(), 2) remote_data = self.default_data - original_data = deepcopy(remote_data) + og_data = deepcopy(remote_data) self.remote_api.sync_remote_data(self.source_site, remote_data) self.assertEqual(Project.objects.all().count(), 2) @@ -1872,7 +1981,7 @@ def test_update_no_changes(self): expected = { 'id': self.c_owner_obj.pk, 'project': self.category_obj.pk, - 'user': self.target_user.pk, + 'user': self.user_target.pk, 'role': self.role_owner.pk, 'sodar_uuid': self.c_owner_obj.sodar_uuid, } @@ -1899,7 +2008,7 @@ def test_update_no_changes(self): expected = { 'id': self.p_owner_obj.pk, 'project': self.project_obj.pk, - 'user': self.target_user.pk, + 'user': self.user_target.pk, 'role': self.role_owner.pk, 'sodar_uuid': self.p_owner_obj.sodar_uuid, } @@ -1963,9 +2072,8 @@ def test_update_no_changes(self): self.assertEqual(peer_project_dict, expected) # Assert no changes between update_data and remote_data - # Except global app settings, they are always updated. - original_data['app_settings'][PR_IP_RESTRICT_UUID]['status'] = 'updated' - original_data['app_settings'][PR_IP_ALLOWLIST_UUID][ - 'status' - ] = 'updated' - self.assertEqual(original_data, remote_data) + # except global app settings, they are always updated + og_data['app_settings'][SET_IP_RESTRICT_UUID]['status'] = 'updated' + og_data['app_settings'][SET_IP_ALLOWLIST_UUID]['status'] = 'updated' + og_data['app_settings'][SET_STAR_UUID]['status'] = 'updated' + self.assertEqual(og_data, remote_data)