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)
Esempio n. 4
0
    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)
Esempio n. 6
0
    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)
Esempio n. 9
0
 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')
Esempio n. 11
0
 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
Esempio n. 12
0
 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)
Esempio n. 14
0
 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')
Esempio n. 16
0
 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
Esempio n. 17
0
 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
Esempio n. 18
0
    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
Esempio n. 19
0
    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
Esempio n. 20
0
    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