def test_reset_all_tasks(self, mock_set_task_history, mock_query, mock_get_project): user_id = 123 test_project = MagicMock(spec=Project) test_project.id = 456 test_project.tasks_mapped = 2 test_project.tasks_validated = 2 test_tasks = [ MagicMock(spec=Task), MagicMock(spec=Task), MagicMock(spec=Task) ] mock_query.return_value.filter.return_value.all.return_value = test_tasks mock_get_project.return_value = test_project ProjectAdminService.reset_all_tasks(test_project.id, user_id) for test_task in test_tasks: test_task.set_task_history.assert_called() test_task.reset_task.assert_called_with(user_id) mock_get_project.assert_called_with(test_project.id) self.assertEqual(test_project.tasks_mapped, 0) self.assertEqual(test_project.tasks_validated, 0) test_project.save.assert_called()
def test_attempting_to_attach_non_existant_license_raise_error( self, license_mock): # Arrange license_mock.side_effect = NotFound() with self.assertRaises(ProjectAdminServiceError): ProjectAdminService._validate_imagery_licence(1)
def test_get_raises_error_if_not_found(self, mock_project): # Arrange mock_project.return_value = None # Act / Assert with self.assertRaises(NotFound): ProjectAdminService._get_project_by_id(12)
def delete(self, project_id): """ Delete a file from a project --- tags: - project-admin produces: - application/json parameters: - name: project_id in: path description: The unique project ID required: true type: integer default: 1 - name: file_id in: query description: The unique file ID required: true type: integer default: null responses: 200: description: File deleted 401: description: Unauthorized - Invalid credentials 500: description: Internal Server Error """ try: file_id = request.args.get('file_id') if request.args.get( 'file_id') else None file = ProjectFiles.get(project_id, file_id) path = file.path file_name = file.file_name except NotFound: return {"Error": "File info not found"}, 404 except Exception as e: error_msg = f'Project File DELETE - unhandled error: {str(e)}' current_app.logger.critical(error_msg) return {"error": error_msg}, 500 try: ProjectAdminService.delete_project_file(project_id, file_id) except NotFound: return {"Error": "Project File Not Found"}, 404 except Exception as e: error_msg = f'Project File DELETE - unhandled error: {str(e)}' current_app.logger.critical(error_msg) return {"error": error_msg}, 500 try: os.remove(os.path.join(path, file_name)) return {"Success": "Project File Deleted"}, 200 except FileNotFoundError: return {"Error": "File not found"}, 404
def test_cant_add_tasks_if_geojson_not_feature_collection(self): # Arrange invalid_feature = '{"coordinates": [[[[-4.0237, 56.0904], [-3.9111, 56.1715], [-3.8122, 56.098],' \ '[-4.0237, 56.0904]]]], "type": "MultiPolygon"}' # Act with self.assertRaises(InvalidGeoJson): ProjectAdminService._attach_tasks_to_project( MagicMock(), invalid_feature)
def post(self, project_id): """ Update project file --- tags: - project-admin produces: - application/json parameters: - in: header name: Authorization description: Base64 encoded session token required: true type: string default: Token sessionTokenHere== - name: project_id in: path description: The unique project ID required: true type: integer default: 1 - name: body in: body description: JSON Object to update project file required: true - name: file_id in: query description: The unique file id type: integer default: null required: true responses: 200: description: File uploaded 401: description: Unauthorized - Invalid credentials 500: description: Internal Server Error """ try: file_id = request.args.get('file_id') if request.args.get( 'file_id') else None dto = ProjectFileDTO(request.get_json()) dto.id = file_id dto.project_id = project_id ProjectAdminService.update_project_file(dto) return {"Success": "Upload Policy Updated"}, 200 except NotFound: return {"Error": "Project File Not Found"}, 404 except Exception as e: error_msg = f'Project File POST - unhandled error: {str(e)}' current_app.logger.critical(error_msg) return {"error": error_msg}, 500
def test_updating_a_private_project_with_no_allowed_users_causes_an_error( self, mock_project): # Arrange mock_project.return_value = Project() dto = ProjectDTO() dto.private = True dto.allowed_usernames = [] with self.assertRaises(ProjectAdminServiceError): ProjectAdminService.update_project(dto)
def test_no_project_info_for_default_locale_raises_error(self): # Arrange locales = [] info = ProjectInfoDTO() info.locale = 'en' info.name = 'Test' locales.append(info) # Act / Assert with self.assertRaises(ProjectAdminServiceError): ProjectAdminService._validate_default_locale('it', locales)
def get(self, project_id): """ Gets all comments for project --- tags: - project-admin produces: - application/json parameters: - name: project_id in: path description: The unique project ID required: true type: integer default: 1 responses: 200: description: Comments found 401: description: Unauthorized - Invalid credentials 404: description: No comments found 500: description: Internal Server Error """ try: comments_dto = ProjectAdminService.get_all_comments(project_id) return comments_dto.to_primitive(), 200 except NotFound: return {"Error": "No comments found"}, 404 except Exception as e: error_msg = f'Project GET - unhandled error: {str(e)}' current_app.logger.critical(error_msg) return {"error": error_msg}, 500
def test_valid_geo_json_attaches_task_to_project(self): # Arrange valid_feature_collection = json.loads( '{"features": [{"geometry": {"coordinates": [[[[-4.0237, 56.0904],' '[-3.9111, 56.1715], [-3.8122, 56.098], [-4.0237, 56.0904]]]], "type":' '"MultiPolygon"}, "properties": {"x": 2402, "y": 1736, "zoom": 12, "splittable": true}, "type":' '"Feature"}], "type": "FeatureCollection"}') test_project = Project() # Act ProjectAdminService._attach_tasks_to_project(test_project, valid_feature_collection) # Assert self.assertEqual(1, test_project.tasks.count(), 'One task should have been attached to project')
def post(self, project_id): """ Transfers a project to a new user. --- tags: - project admin produces: - application/json parameters: - in: header name: Authorization description: Base64 encoded session token required: true type: string default: Token sessionTokenHere== - name: project_id in: path description: The unique project ID required: true type: integer default: 1 - in: body name: body required: true description: the username of the new owner schema: properties: username: type: string responses: 200: description: All tasks reset 401: description: Unauthorized - Invalid credentials 500: description: Internal Server Error """ try: username = request.get_json()['username'] ProjectAdminService.transfer_project_to(project_id, tm.authenticated_user_id, username) return {"Success": "Project Transfered"}, 200 except Exception as e: error_msg = f'Project GET - unhandled error: {str(e)}' current_app.logger.critical(error_msg) return {"error": error_msg}, 500
def delete(self, project_id): """ Deletes a Tasking-Manager project --- tags: - project-admin produces: - application/json parameters: - in: header name: Authorization description: Base64 encoded session token required: true type: string default: Token sessionTokenHere== - name: project_id in: path description: The unique project ID required: true type: integer default: 1 responses: 200: description: Project deleted 401: description: Unauthorized - Invalid credentials 403: description: Forbidden - users have submitted mapping 404: description: Project not found 500: description: Internal Server Error """ try: ProjectAdminService.delete_project(project_id) return {"Success": "Project deleted"}, 200 except ProjectAdminServiceError: return {"Error": "Project has some mapping"}, 403 except NotFound: return {"Error": "Project Not Found"}, 404 except Exception as e: error_msg = f'Project GET - unhandled error: {str(e)}' current_app.logger.critical(error_msg) return {"error": error_msg}, 500
def test_published_project_with_incomplete_default_locale_raises_error( self, mock_project): # 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 # Act / Assert with self.assertRaises(ProjectAdminServiceError): ProjectAdminService.update_project(dto)
def post(self, project_id): """ Reset all tasks on project back to ready, preserving history. --- tags: - project-admin produces: - application/json parameters: - in: header name: Authorization description: Base64 encoded session token required: true type: string default: Token sessionTokenHere== - name: project_id in: path description: The unique project ID required: true type: integer default: 1 responses: 200: description: All tasks reset 401: description: Unauthorized - Invalid credentials 500: description: Internal Server Error """ try: ProjectAdminService.reset_all_tasks(project_id, tm.authenticated_user_id) return {"Success": "All tasks reset"}, 200 except Exception as e: error_msg = f'Project GET - unhandled error: {str(e)}' current_app.logger.critical(error_msg) return {"error": error_msg}, 500
def test_complete_default_locale_raises_is_valid(self): # Arrange locales = [] info = ProjectInfoDTO() info.locale = 'en' info.name = 'Test' info.description = 'Test Desc' info.short_description = 'Short Desc' info.instructions = 'Instruct' locales.append(info) # Act is_valid = ProjectAdminService._validate_default_locale('en', locales) # Assert self.assertTrue(is_valid, 'Complete default locale should be valid')
def get(self): """ Get all projects for logged in admin --- tags: - project-admin produces: - application/json parameters: - in: header name: Authorization description: Base64 encoded session token required: true type: string default: Token sessionTokenHere== - in: header name: Accept-Language description: Language user is requesting type: string required: true default: en responses: 200: description: All mapped tasks validated 401: description: Unauthorized - Invalid credentials 404: description: Admin has no projects 500: description: Internal Server Error """ try: admin_projects = ProjectAdminService.get_projects_for_admin( tm.authenticated_user_id, request.environ.get('HTTP_ACCEPT_LANGUAGE')) return admin_projects.to_primitive(), 200 except NotFound: return {"Error": "No comments found"}, 404 except Exception as e: error_msg = f'Project GET - unhandled error: {str(e)}' current_app.logger.critical(error_msg) return {"error": error_msg}, 500
def get(self, project_id): """ Retrieves a Tasking-Manager project --- tags: - project-admin produces: - application/json parameters: - in: header name: Authorization description: Base64 encoded session token required: true type: string default: Token sessionTokenHere== - name: project_id in: path description: The unique project ID required: true type: integer default: 1 responses: 200: description: Project found 401: description: Unauthorized - Invalid credentials 404: description: Project not found 500: description: Internal Server Error """ try: project_dto = ProjectAdminService.get_project_dto_for_admin( project_id) return project_dto.to_primitive(), 200 except NotFound: return {"Error": "Project Not Found"}, 404 except Exception as e: error_msg = f'Project GET - unhandled error: {str(e)}' current_app.logger.critical(error_msg) return {"error": error_msg}, 500
def post(self, project_id): """ Updates a Tasking-Manager project --- tags: - project-admin produces: - application/json parameters: - in: header name: Authorization description: Base64 encoded session token required: true type: string default: Token sessionTokenHere== - name: project_id in: path description: The unique project ID required: true type: integer default: 1 - in: body name: body required: true description: JSON object for creating draft project schema: properties: projectStatus: type: string default: DRAFT projectPriority: type: string default: MEDIUM defaultLocale: type: string default: en mapperLevel: type: string default: BEGINNER enforceMapperLevel: type: boolean default: false enforceValidatorRole: type: boolean default: false private: type: boolean default: false changesetComment: type: string default: hotosm-project-1 entitiesToMap: type: string default: Buildings only dueDate: type: date default: "2017-04-11T12:38:49" imagery: type: string default: http//www.bing.com/maps/ josmPreset: type: string default: josm preset goes here mappingTypes: type: array items: type: string default: [BUILDINGS, ROADS] campaignTag: type: string default: malaria organisationTag: type: string default: red cross licenseId: type: integer default: 1 description: Id of imagery license associated with the project allowedUsernames: type: array items: type: string default: ["Iain Hunter", LindaA1] priorityAreas: type: array items: schema: $ref: "#/definitions/GeoJsonPolygon" projectInfoLocales: type: array items: schema: $ref: "#/definitions/ProjectInfo" responses: 200: description: Project updated 400: description: Client Error - Invalid Request 401: description: Unauthorized - Invalid credentials 404: description: Project not found 500: description: Internal Server Error """ try: project_dto = ProjectDTO(request.get_json()) project_dto.project_id = project_id project_dto.validate() except DataError as e: current_app.logger.error(f'Error validating request: {str(e)}') return str(e), 400 try: ProjectAdminService.update_project(project_dto) return {"Status": "Updated"}, 200 except InvalidGeoJson as e: return {"Invalid GeoJson": str(e)}, 400 except NotFound: return {"Error": "Project Not Found"}, 404 except ProjectAdminServiceError as e: return {"error": str(e)}, 400 except Exception as e: error_msg = f'Project GET - unhandled error: {str(e)}' current_app.logger.critical(error_msg) return {"error": error_msg}, 500
def put(self): """ Creates a tasking-manager project --- tags: - project-admin produces: - application/json parameters: - in: header name: Authorization description: Base64 encoded session token required: true type: string default: Token sessionTokenHere== - in: body name: body required: true description: JSON object for creating draft project schema: properties: projectName: type: string default: HOT Project areaOfInterest: schema: properties: type: type: string default: FeatureCollection features: type: array items: schema: $ref: "#/definitions/GeoJsonFeature" tasks: schema: properties: type: type: string default: FeatureCollection features: type: array items: schema: $ref: "#/definitions/GeoJsonFeature" arbitraryTasks: type: boolean default: false responses: 201: description: Draft project created successfully 400: description: Client Error - Invalid Request 401: description: Unauthorized - Invalid credentials 500: description: Internal Server Error """ try: draft_project_dto = DraftProjectDTO(request.get_json()) draft_project_dto.user_id = tm.authenticated_user_id draft_project_dto.validate() except DataError as e: current_app.logger.error(f'error validating request: {str(e)}') return str(e), 400 try: draft_project_id = ProjectAdminService.create_draft_project( draft_project_dto) return {"projectId": draft_project_id}, 201 except (InvalidGeoJson, InvalidData) as e: return {"error": f'{str(e)}'}, 400 except Exception as e: error_msg = f'Project PUT - unhandled error: {str(e)}' current_app.logger.critical(error_msg) return {"error": error_msg}, 500
def put(self, project_id): """ Save a new file for a project --- tags: - project-admin produces: - application/json parameters: - in: header name: Authorization description: Base64 encoded session token required: true type: string default: Token sessionTokenHere== - name: project_id in: path description: The unique project ID required: true type: integer default: 1 - name: upload_policy in: query description: The upload policy type: string default: ALLOW - name: file in: formData description: The file to be saved required: true type: file responses: 200: description: File uploaded 401: description: Unauthorized - Invalid credentials 500: description: Internal Server Error """ try: project_file_dto = ProjectFileDTO() file = request.files['file'] if file: file_name = secure_filename(file.filename) path = os.path.join(current_app.config['PROJECT_FILES_DIR'], str(project_id)) if not os.path.exists(path): os.makedirs(path) file_path = os.path.join(path, file_name) file.save(file_path) project_file_dto.path = path project_file_dto.project_id = project_id project_file_dto.file_name = file_name if request.args['upload_policy']: print(UploadPolicy[request.args['upload_policy']]) project_file_dto.upload_policy = UploadPolicy[ request.args['upload_policy'].upper()].name project_file_dto.validate() except DataError as e: current_app.logger.error(f'error validating request: {str(e)}') return str(e), 400 try: ProjectAdminService.create_project_file(project_file_dto) return {"Success": "File uploaded"}, 200 except FileExistsError: return {"Error": "Project already has this file"}, 403 except NotFound: return {"Error": "Project Not Found"}, 404 except Exception as e: error_msg = f'Project Files POST - unhandled error: {str(e)}' current_app.logger.critical(error_msg) return {"error": error_msg}, 500