class TestUser(BaseTestCase): def setUp(self): super().setUp() self.test_user = User() self.test_user.role = UserRole.MAPPER.value self.test_user.id = 12 self.test_user.mapping_level = MappingLevel.BEGINNER.value self.test_user.username = "******" self.test_user.email_address = "*****@*****.**" def test_as_dto_will_not_return_email_if_not_owner(self): # if self.skip_tests: # return # Act user_dto = self.test_user.as_dto("mastertest") # Assert self.assertFalse(user_dto.email_address) def test_as_dto_will_not_return_email_if_owner(self): # if self.skip_tests: # return # Act user_dto = self.test_user.as_dto("mrtest") # Assert self.assertTrue(user_dto.email_address)
def test_published_project_with_incomplete_default_locale_raises_error( self, mock_project, mock_user): # Arrange stub_project = Project() stub_project.status = ProjectStatus.PUBLISHED.value mock_project.return_value = stub_project locales = [] info = ProjectInfoDTO() info.locale = "en" info.name = "Test" locales.append(info) dto = ProjectDTO() dto.project_id = 1 dto.default_locale = "en" dto.project_info_locales = locales dto.project_status = ProjectStatus.PUBLISHED.name stub_admin_user = User() stub_admin_user.username = "******" stub_admin_user.role = UserRole.ADMIN.value mock_user.return_value = stub_admin_user # Act / Assert with self.assertRaises(ProjectAdminServiceError): ProjectAdminService.update_project(dto, mock_user.id)
def test_admin_role_is_recognized_as_a_validator(self, mock_user): # Arrange stub_user = User() stub_user.role = UserRole.ADMIN.value mock_user.return_value = stub_user # Act / Assert self.assertTrue(UserService.is_user_validator(123))
def setUp(self): super().setUp() self.test_user = User() self.test_user.role = UserRole.MAPPER.value self.test_user.id = 12 self.test_user.mapping_level = MappingLevel.BEGINNER.value self.test_user.username = "******" self.test_user.email_address = "*****@*****.**"
def test_mapper_role_is_not_recognized_as_a_validator(self, mock_user): # Arrange stub_user = User() stub_user.role = UserRole.MAPPER.value mock_user.return_value = stub_user # Act / Assert self.assertFalse(UserService.is_user_validator(123))
def test_update_after_flagging_bad_imagery(self): # Arrange test_project = Project() test_project.tasks_bad_imagery = 0 test_user = User() test_user.tasks_invalidated = 0 # Act test_project, test_user = StatsService._update_tasks_stats( test_project, test_user, TaskStatus.READY, TaskStatus.BADIMAGERY) # Assert self.assertEqual(test_project.tasks_bad_imagery, 1)
def test_update_after_mapping_increments_counter(self): # Arrange test_project = Project() test_project.tasks_mapped = 0 test_user = User() test_user.tasks_mapped = 0 # Act test_project, test_user = StatsService._update_tasks_stats( test_project, test_user, TaskStatus.READY, TaskStatus.MAPPED) # Assert self.assertEqual(test_project.tasks_mapped, 1) self.assertEqual(test_user.tasks_mapped, 1)
def test_same_state_keeps_counter(self): # Arrange test_project = Project() test_project.tasks_mapped = 0 test_user = User() test_user.tasks_mapped = 0 # Act test_project, test_user = StatsService._update_tasks_stats( test_project, test_user, TaskStatus.MAPPED, TaskStatus.MAPPED) # Assert self.assertEqual(test_project.tasks_mapped, 0) self.assertEqual(test_user.tasks_mapped, 0)
def get_user_by_id(user_id: int) -> User: user = User.get_by_id(user_id) if user is None: raise NotFound() return user
def test_update_after_invalidating_bad_imagery_task_sets_counters_correctly(self): # Arrange test_project = Project() test_project.tasks_bad_imagery = 1 test_user = User() test_user.tasks_invalidated = 0 # Act test_project, test_user = StatsService._update_tasks_stats( test_project, test_user, TaskStatus.BADIMAGERY, TaskStatus.INVALIDATED ) # Assert self.assertEqual(test_project.tasks_bad_imagery, 0) self.assertEqual(test_user.tasks_invalidated, 1)
def test_project_search(self): user = User(id=3488526) search_dto = ProjectSearchDTO() search_dto.preferred_locale = None search_dto.mapper_level = "BEGINNER" search_dto.mapping_types = None search_dto.project_statuses = ["PUBLISHED"] search_dto.organisation_name = None search_dto.organisation_id = 34 search_dto.team_id = 1 search_dto.campaign = "DengueFeverHonduras" search_dto.order_by = "priority" search_dto.order_by_type = "DESC" search_dto.country = "Honduras" search_dto.page = 1 search_dto.text_search = "6158" search_dto.mapping_editors = ["ID"] search_dto.validation_editors = ["ID"] search_dto.teams = None search_dto.interests = None search_dto.created_by = 378610 search_dto.mapped_by = None search_dto.favorited_by = None search_dto.managed_by = None search_dto.omit_map_results = False search_dto.validate() self.assertIsNotNone( ProjectSearchService.search_projects(search_dto, user))
def get_user_by_username(username: str) -> User: user = User.get_by_username(username) if user is None: raise NotFound() return user
def update(self, organisation_dto: OrganisationDTO): """ Updates Organisation from DTO """ for attr, value in organisation_dto.items(): if attr == "type" and value is not None: value = OrganisationType[organisation_dto.type].value if attr == "managers": continue try: is_field_nullable = self.__table__.columns[attr].nullable if is_field_nullable and value is not None: setattr(self, attr, value) elif value is not None: setattr(self, attr, value) except KeyError: continue if organisation_dto.managers: self.managers = [] # Need to handle this in the loop so we can take care of NotFound users for manager in organisation_dto.managers: new_manager = User.get_by_username(manager) if new_manager is None: raise NotFound(f"User {manager} Not Found") self.managers.append(new_manager) db.session.commit()
def check_draft_project_visibility(self, authenticated_user_id: int): """" Check if a User is allowed to see a Draft Project """ is_allowed_user = False if authenticated_user_id: is_team_manager = False user = User.get_by_id(authenticated_user_id) user_orgs = Organisation.get_organisations_managed_by_user( authenticated_user_id ) if self.teams: for project_team in self.teams: team_members = Team.get(project_team.team_id)._get_team_members() for member in team_members: if ( user.username == member["username"] and member["function"] == TeamMemberFunctions.MANAGER.name ): is_team_manager = True break if ( UserRole(user.role) == UserRole.ADMIN or authenticated_user_id == self.author_id or self.organisation in user_orgs or is_team_manager ): is_allowed_user = True return is_allowed_user
def test_update_after_invalidating_mapped_task_sets_counters_correctly( self): # Arrange test_project = Project() test_project.tasks_mapped = 1 test_user = User() test_user.tasks_invalidated = 0 # Act test_project, test_user = StatsService._update_tasks_stats( test_project, test_user, TaskStatus.MAPPED, TaskStatus.INVALIDATED) # Assert self.assertEqual(test_project.tasks_mapped, 0) self.assertEqual(test_user.tasks_invalidated, 1)
def test_updating_a_private_project_with_no_allowed_users_causes_an_error( self, mock_project, mock_user): # Arrange mock_project.return_value = Project() dto = ProjectDTO() dto.private = True dto.allowed_usernames = [] stub_user = User() stub_user.username = "******" stub_user.role = UserRole.ADMIN.value mock_user.return_value = stub_user with self.assertRaises(ProjectAdminServiceError): ProjectAdminService.update_project(dto, mock_user.id)
def as_dto_team_member(self, member) -> TeamMembersDTO: """ Returns a dto for the team member""" member_dto = TeamMembersDTO() user = User.get_by_id(member.user_id) member_function = TeamMemberFunctions(member.function).name member_dto.username = user.username member_dto.function = member_function member_dto.picture_url = user.picture_url member_dto.active = member.active return member_dto
def test_get_intersecting_projects( self, get_dto_for_locale, _get_intersecting_projects, get_user_by_username, validate_bbox_area, _make_4326_polygon_from_bbox, ): if self.skip_tests: return # arrange _make_4326_polygon_from_bbox mock _make_4326_polygon_from_bbox.return_value = Polygon([ (34.68826225820438, -12.59912449955007), (34.68826225820438, -11.57858317689196), (32.50198296132938, -11.57858317689196), (32.50198296132938, -12.59912449955007), (34.68826225820438, -12.59912449955007), ]) # arrange validate_bbox_area mock validate_bbox_area.return_value = True # arrange get_user_by_username mock get_user_by_username.return_value = User(id=3488526) # arrange _get_intersecting_projects mock polygon = json.dumps(get_canned_json("search_bbox_feature.json")) project = Project(id=2274, status=0, default_locale="en", geometry=polygon) projects = [project] _get_intersecting_projects.return_value = projects # arrange get_dto_for_locale mock get_dto_for_locale.return_value = ProjectInfo( name="PEPFAR Kenya: Homa Bay") # arrange dto dto = ProjectSearchBBoxDTO() dto.bbox = map(float, "34.404,-1.034, 34.717,-0.624".split(",")) dto.preferred_locale = "en" dto.input_srid = 4326 dto.project_author = 3488526 dto.validate() # arrange expected result expected = json.dumps(get_canned_json("search_bbox_result.json")) # act # result = ProjectSearchService.get_projects_geojson(dto) # assert self.assertEqual(str(expected), str(expected))
def create_canned_user() -> User: """ Generate a canned user in the DB """ test_user = User() test_user.id = TEST_USER_ID test_user.username = "******" test_user.mapping_level = 1 test_user.create() return test_user
def test_updating_a_private_project_with_no_allowed_users( self, mock_project, mock_project2, mock_user): # Arrange mock_project.return_value = Project() dto = ProjectDTO() dto.private = True dto.allowed_usernames = [] stub_user = User() stub_user.username = "******" stub_user.role = UserRole.ADMIN.value mock_user.return_value = stub_user try: ProjectAdminService.update_project(dto, mock_user.id) except ProjectAdminServiceError: self.fail( "update_project raised an exception when setting it as private" )
def refresh_mapper_level() -> int: """ Helper function to run thru all users in the DB and update their mapper level """ users = User.get_all_users_not_paginated() users_updated = 1 total_users = len(users) for user in users: UserService.check_and_update_mapper_level(user.id) if users_updated % 50 == 0: print(f"{users_updated} users updated of {total_users}") users_updated += 1 return users_updated
def as_dto_for_mapping( self, authenticated_user_id: int = None, locale: str = "en", abbrev: bool = True ) -> Optional[ProjectDTO]: """ Creates a Project DTO suitable for transmitting to mapper users """ # Check for project visibility settings is_allowed_user = True if self.status == ProjectStatus.DRAFT.value: if not self.check_draft_project_visibility(authenticated_user_id): is_allowed_user = False if self.private: is_allowed_user = False if authenticated_user_id: user = User.get_by_id(authenticated_user_id) if ( UserRole(user.role) == UserRole.ADMIN or authenticated_user_id == self.author_id ): is_allowed_user = True for user in self.allowed_users: if user.id == authenticated_user_id: is_allowed_user = True break if is_allowed_user: project, project_dto = self._get_project_and_base_dto() if abbrev is False: project_dto.tasks = Task.get_tasks_as_geojson_feature_collection( self.id, None ) else: project_dto.tasks = Task.get_tasks_as_geojson_feature_collection_no_geom( self.id ) project_dto.project_info = ProjectInfo.get_dto_for_locale( self.id, locale, project.default_locale ) if project.organisation_id: project_dto.organisation = project.organisation.id project_dto.organisation_name = project.organisation.name project_dto.organisation_logo = project.organisation.logo project_dto.project_info_locales = ProjectInfo.get_dto_for_all_locales( self.id ) return project_dto
def create_from_dto(cls, new_organisation_dto: NewOrganisationDTO): """ Creates a new organisation from a DTO """ new_org = cls() new_org.name = new_organisation_dto.name new_org.logo = new_organisation_dto.logo new_org.url = new_organisation_dto.url for manager in new_organisation_dto.managers: user = User.get_by_username(manager) if user is None: raise NotFound(f"User {manager} Not Found") new_org.managers.append(user) new_org.create() return new_org
def setUp(self): # Arrange test_user = User() test_user.role = UserRole.MAPPER.value test_user.id = 12 test_user.mapping_level = MappingLevel.BEGINNER.value test_user.username = "******" test_user.email_address = "*****@*****.**" self.test_user = test_user """ Setup test context so we can connect to database """ self.app = create_app() self.ctx = self.app.app_context() self.ctx.push() if self.skip_tests: return
def create_from_dto(cls, new_organisation_dto: NewOrganisationDTO): """ Creates a new organisation from a DTO """ new_org = cls() new_org.name = new_organisation_dto.name new_org.slug = new_organisation_dto.slug or slugify(new_organisation_dto.name) new_org.logo = new_organisation_dto.logo new_org.description = new_organisation_dto.description new_org.url = new_organisation_dto.url new_org.type = OrganisationType[new_organisation_dto.type].value new_org.subscription_tier = new_organisation_dto.subscription_tier for manager in new_organisation_dto.managers: user = User.get_by_username(manager) if user is None: raise NotFound(f"User {manager} Not Found") new_org.managers.append(user) new_org.create() return new_org
def update(self, team_dto: TeamDTO): """ Updates Team from DTO """ if team_dto.organisation: self.organisation = Organisation().get_organisation_by_name( team_dto.organisation) for attr, value in team_dto.items(): if attr == "visibility" and value is not None: value = TeamVisibility[team_dto.visibility].value if attr in ("members", "organisation"): continue try: is_field_nullable = self.__table__.columns[attr].nullable if is_field_nullable and value is not None: setattr(self, attr, value) elif value is not None: setattr(self, attr, value) except KeyError: continue if team_dto.members != self._get_team_members() and team_dto.members: for member in self.members: db.session.delete(member) for member in team_dto.members: user = User.get_by_username(member["userName"]) if user is None: raise NotFound("User not found") new_team_member = TeamMembers() new_team_member.team = self new_team_member.member = user new_team_member.function = TeamMemberFunctions[ member["function"]].value db.session.commit()
def test_updating_a_project_with_different_roles(self, mock_project, mock_project2, mock_user): stub_project = Project() stub_project.status = ProjectStatus.DRAFT.value mock_project.return_value = stub_project locales = [] info = ProjectInfoDTO() info.locale = "en" info.name = "Test" locales.append(info) dto = ProjectDTO() dto.project_id = 1 dto.default_locale = "en" dto.project_status = ProjectStatus.DRAFT.name dto.project_priority = ProjectPriority.LOW.name dto.mapper_level = MappingLevel.BEGINNER.name dto.mapping_types = ["ROADS"] dto.mapping_editors = ["ID"] dto.validation_editors = ["ID"] dto.project_info_locales = locales stub_user = User() stub_user.username = "******" stub_user.role = UserRole.MAPPER.value mock_user.return_value = stub_user with self.assertRaises(ValueError) as e: ProjectAdminService.update_project(dto, mock_user.id) the_exception = e.exception self.assertTrue(isinstance(the_exception, ValueError)) # stub_project.author_id = mock_user.id stub_user.username = "******" stub_user.role = UserRole.ADMIN.value mock_user.return_value = stub_user try: ProjectAdminService.update_project(dto, mock_user.id) except ProjectAdminServiceError: self.fail("update_project raised an exception with admin role")
def _get_project_and_base_dto(self): """ Populates a project DTO with properties common to all roles """ base_dto = ProjectDTO() base_dto.project_id = self.id base_dto.project_status = ProjectStatus(self.status).name base_dto.default_locale = self.default_locale base_dto.project_priority = ProjectPriority(self.priority).name base_dto.area_of_interest = self.get_aoi_geometry_as_geojson() base_dto.aoi_bbox = shape(base_dto.area_of_interest).bounds base_dto.mapping_permission = MappingPermission( self.mapping_permission).name base_dto.validation_permission = ValidationPermission( self.validation_permission).name base_dto.enforce_random_task_selection = self.enforce_random_task_selection base_dto.private = self.private base_dto.mapper_level = MappingLevel(self.mapper_level).name base_dto.changeset_comment = self.changeset_comment base_dto.osmcha_filter_id = self.osmcha_filter_id base_dto.due_date = self.due_date base_dto.imagery = self.imagery base_dto.josm_preset = self.josm_preset base_dto.id_presets = self.id_presets base_dto.country_tag = self.country base_dto.organisation_id = self.organisation_id base_dto.license_id = self.license_id base_dto.created = self.created base_dto.last_updated = self.last_updated base_dto.author = User.get_by_id(self.author_id).username base_dto.active_mappers = Project.get_active_mappers(self.id) base_dto.task_creation_mode = TaskCreationMode( self.task_creation_mode).name base_dto.percent_mapped = Project.calculate_tasks_percent( "mapped", self.total_tasks, self.tasks_mapped, self.tasks_validated, self.tasks_bad_imagery, ) base_dto.percent_validated = Project.calculate_tasks_percent( "validated", self.total_tasks, self.tasks_mapped, self.tasks_validated, self.tasks_bad_imagery, ) base_dto.percent_bad_imagery = Project.calculate_tasks_percent( "bad_imagery", self.total_tasks, self.tasks_mapped, self.tasks_validated, self.tasks_bad_imagery, ) base_dto.project_teams = [ ProjectTeamDTO( dict( team_id=t.team.id, team_name=t.team.name, role=TeamRoles(t.role).name, )) for t in self.teams ] if self.custom_editor: base_dto.custom_editor = self.custom_editor.as_dto() if self.private: # If project is private it should have a list of allowed users allowed_usernames = [] for user in self.allowed_users: allowed_usernames.append(user.username) base_dto.allowed_usernames = allowed_usernames if self.mapping_types: mapping_types = [] for mapping_type in self.mapping_types: mapping_types.append(MappingTypes(mapping_type).name) base_dto.mapping_types = mapping_types if self.campaign: base_dto.campaigns = [i.as_dto() for i in self.campaign] if self.mapping_editors: mapping_editors = [] for mapping_editor in self.mapping_editors: mapping_editors.append(Editors(mapping_editor).name) base_dto.mapping_editors = mapping_editors if self.validation_editors: validation_editors = [] for validation_editor in self.validation_editors: validation_editors.append(Editors(validation_editor).name) base_dto.validation_editors = validation_editors if self.priority_areas: geojson_areas = [] for priority_area in self.priority_areas: geojson_areas.append(priority_area.get_as_geojson()) base_dto.priority_areas = geojson_areas base_dto.interests = [ InterestDTO(dict(id=i.id, name=i.name)) for i in self.interests ] return self, base_dto
def get_project_summary(self, preferred_locale) -> ProjectSummary: """ Create Project Summary model for postgis project object""" summary = ProjectSummary() summary.project_id = self.id priority = self.priority if priority == 0: summary.priority = "URGENT" elif priority == 1: summary.priority = "HIGH" elif priority == 2: summary.priority = "MEDIUM" else: summary.priority = "LOW" summary.author = User.get_by_id(self.author_id).username summary.default_locale = self.default_locale summary.country_tag = self.country summary.changeset_comment = self.changeset_comment summary.due_date = self.due_date summary.created = self.created summary.last_updated = self.last_updated summary.osmcha_filter_id = self.osmcha_filter_id summary.mapper_level = MappingLevel(self.mapper_level).name summary.mapping_permission = MappingPermission( self.mapping_permission).name summary.validation_permission = ValidationPermission( self.validation_permission).name summary.random_task_selection_enforced = self.enforce_random_task_selection summary.private = self.private summary.license_id = self.license_id summary.status = ProjectStatus(self.status).name summary.id_presets = self.id_presets summary.imagery = self.imagery if self.organisation_id: summary.organisation = self.organisation_id summary.organisation_name = self.organisation.name summary.organisation_logo = self.organisation.logo if self.campaign: summary.campaigns = [i.as_dto() for i in self.campaign] # Cast MappingType values to related string array mapping_types_array = [] if self.mapping_types: for mapping_type in self.mapping_types: mapping_types_array.append(MappingTypes(mapping_type).name) summary.mapping_types = mapping_types_array if self.mapping_editors: mapping_editors = [] for mapping_editor in self.mapping_editors: mapping_editors.append(Editors(mapping_editor).name) summary.mapping_editors = mapping_editors if self.validation_editors: validation_editors = [] for validation_editor in self.validation_editors: validation_editors.append(Editors(validation_editor).name) summary.validation_editors = validation_editors if self.custom_editor: summary.custom_editor = self.custom_editor.as_dto() # If project is private, fetch list of allowed users if self.private: allowed_users = [] for user in self.allowed_users: allowed_users.append(user.username) summary.allowed_users = allowed_users centroid_geojson = db.session.scalar(self.centroid.ST_AsGeoJSON()) summary.aoi_centroid = geojson.loads(centroid_geojson) summary.percent_mapped = Project.calculate_tasks_percent( "mapped", self.total_tasks, self.tasks_mapped, self.tasks_validated, self.tasks_bad_imagery, ) summary.percent_validated = Project.calculate_tasks_percent( "validated", self.total_tasks, self.tasks_mapped, self.tasks_validated, self.tasks_bad_imagery, ) summary.percent_bad_imagery = Project.calculate_tasks_percent( "bad_imagery", self.total_tasks, self.tasks_mapped, self.tasks_validated, self.tasks_bad_imagery, ) summary.project_teams = [ ProjectTeamDTO( dict( team_id=t.team.id, team_name=t.team.name, role=TeamRoles(t.role).name, )) for t in self.teams ] project_info = ProjectInfo.get_dto_for_locale(self.id, preferred_locale, self.default_locale) summary.project_info = project_info return summary
def post(self, team_id): """ Removes a user from a team --- tags: - teams produces: - application/json parameters: - in: header name: Authorization description: Base64 encoded session token required: true type: string default: Token sessionTokenHere== - name: team_id in: path description: Unique team ID required: true type: integer default: 1 - in: body name: body required: true description: JSON object to remove user from team schema: properties: username: type: string default: 1 required: true responses: 200: description: Member deleted 403: description: Forbidden, if user attempting to ready other messages 404: description: Not found 500: description: Internal Server Error """ try: authenticated_user_id = token_auth.current_user() username = request.get_json(force=True)["username"] request_user = User.get_by_id(authenticated_user_id) if (TeamService.is_user_team_manager(team_id, authenticated_user_id) or request_user.username == username): TeamService.leave_team(team_id, username) return {"Success": "User removed from the team"}, 200 else: return ( { "Error": "You don't have permissions to remove {} from this team." .format(username) }, 403, ) except NotFound: return {"Error": "No team member found"}, 404 except Exception as e: error_msg = f"TeamMembers DELETE - unhandled error: {str(e)}" current_app.logger.critical(error_msg) return {"Error": error_msg}, 500