コード例 #1
0
 def setUp(self):
     super(TestBaseAnalyst, self).setUp()
     BaseAnalyst.__abstractmethods__ = frozenset()
     self.ctx = ContextFixtures()
     self.base_analyst = BaseAnalyst()
     self.project_repo = ProjectRepository(db)
     self.result_repo = ResultRepository(db)
     self.task_repo = TaskRepository(db)
     assert_dict_equal.__self__.maxDiff = None
     assert_equal.__self__.maxDiff = None
コード例 #2
0
ファイル: test_z3950.py プロジェクト: LibCrowds/pybossa-lc
 def setUp(self):
     super(TestZ3950Analyst, self).setUp()
     self.ctx = ContextFixtures()
     self.z3950_analyst = Z3950Analyst()
     self.result_repo = ResultRepository(db)
     self.task_repo = TaskRepository(db)
     self.data = {
         'user_id': [1],
         'control_number': ['123'],
         'reference': ['abc'],
         'foo': ['bar'],
         'comments': ['Some comment']
     }
コード例 #3
0
ファイル: core.py プロジェクト: vgutierrez9/pybossa
def setup_repositories(app):
    """Setup repositories."""
    from pybossa.repositories import UserRepository
    from pybossa.repositories import ProjectRepository
    from pybossa.repositories import ProjectStatsRepository
    from pybossa.repositories import AnnouncementRepository
    from pybossa.repositories import BlogRepository
    from pybossa.repositories import TaskRepository
    from pybossa.repositories import AuditlogRepository
    from pybossa.repositories import WebhookRepository
    from pybossa.repositories import ResultRepository
    from pybossa.repositories import HelpingMaterialRepository
    global user_repo
    global project_repo
    global project_stats_repo
    global announcement_repo
    global blog_repo
    global task_repo
    global auditlog_repo
    global webhook_repo
    global result_repo
    global helping_repo
    language = app.config.get('FULLTEXTSEARCH_LANGUAGE')
    user_repo = UserRepository(db)
    project_repo = ProjectRepository(db)
    project_stats_repo = ProjectStatsRepository(db)
    announcement_repo = AnnouncementRepository(db)
    blog_repo = BlogRepository(db)
    task_repo = TaskRepository(db, language)
    auditlog_repo = AuditlogRepository(db)
    webhook_repo = WebhookRepository(db)
    result_repo = ResultRepository(db)
    helping_repo = HelpingMaterialRepository(db)
コード例 #4
0
def setup_repositories():
    """Setup repositories."""
    from pybossa.repositories import UserRepository
    from pybossa.repositories import ProjectRepository
    from pybossa.repositories import BlogRepository
    from pybossa.repositories import TaskRepository
    from pybossa.repositories import AuditlogRepository
    from pybossa.repositories import WebhookRepository
    from pybossa.repositories import ResultRepository
    from pybossa.repositories import UserScoreRepository
    global user_repo
    global project_repo
    global blog_repo
    global task_repo
    global auditlog_repo
    global webhook_repo
    global result_repo
    global user_score_repo
    user_repo = UserRepository(db)
    project_repo = ProjectRepository(db)
    blog_repo = BlogRepository(db)
    task_repo = TaskRepository(db)
    auditlog_repo = AuditlogRepository(db)
    webhook_repo = WebhookRepository(db)
    result_repo = ResultRepository(db)
    user_score_repo = UserScoreRepository(db)
コード例 #5
0
ファイル: core.py プロジェクト: shreezus/pybossa
def setup_repositories(app):
    """Setup repositories."""
    from pybossa.repositories import UserRepository
    from pybossa.repositories import ProjectRepository
    from pybossa.repositories import ProjectStatsRepository
    from pybossa.repositories import AnnouncementRepository
    from pybossa.repositories import BlogRepository
    from pybossa.repositories import TaskRepository
    from pybossa.repositories import AuditlogRepository
    from pybossa.repositories import WebhookRepository
    from pybossa.repositories import ResultRepository
    from pybossa.repositories import HelpingMaterialRepository
    from pybossa.repositories import PerformanceStatsRepository
    global user_repo
    global project_repo
    global project_stats_repo
    global announcement_repo
    global blog_repo
    global task_repo
    global auditlog_repo
    global webhook_repo
    global result_repo
    global helping_repo
    global performance_stats_repo
    language = app.config.get('FULLTEXTSEARCH_LANGUAGE')
    rdancy_upd_exp = app.config.get('REDUNDANCY_UPDATE_EXPIRATION', 30)
    user_repo = UserRepository(db)
    project_repo = ProjectRepository(db)
    project_stats_repo = ProjectStatsRepository(db)
    announcement_repo = AnnouncementRepository(db)
    blog_repo = BlogRepository(db)
    task_repo = TaskRepository(db, language, rdancy_upd_exp)
    auditlog_repo = AuditlogRepository(db)
    webhook_repo = WebhookRepository(db)
    result_repo = ResultRepository(db)
    helping_repo = HelpingMaterialRepository(db)
    performance_stats_repo = PerformanceStatsRepository(db)
コード例 #6
0
 def setUp(self):
     super(TestResultAuthorization, self).setUp()
     self.result_repo = ResultRepository(db)
コード例 #7
0
class TestResultAuthorization(Test):

    mock_anonymous = mock_current_user()
    mock_authenticated = mock_current_user(anonymous=False, admin=False, id=2)
    mock_pro = mock_current_user(anonymous=False, admin=False, id=2, pro=True)
    mock_admin = mock_current_user(anonymous=False, admin=True, id=1)
    mock_owner = mock_current_user(anonymous=False, admin=False, id=1)

    def setUp(self):
        super(TestResultAuthorization, self).setUp()
        self.result_repo = ResultRepository(db)

    def create_result(self, n_answers=1, filter_by=False):
        task = TaskFactory.create(n_answers=n_answers)
        TaskRunFactory.create(task=task)
        if filter_by:
            return self.result_repo.filter_by(project_id=1)
        else:
            return self.result_repo.get_by(project_id=1)

    @with_context
    @patch('pybossa.auth.current_user', new=mock_anonymous)
    def test_anonymous_user_can_read_result(self):
        """Test anonymous users can read results"""

        result = self.create_result()

        assert ensure_authorized_to('read', result)

    @with_context
    @patch('pybossa.auth.current_user', new=mock_authenticated)
    def test_auth_user_can_read_result(self):
        """Test auth users can read results"""

        result = self.create_result()

        assert ensure_authorized_to('read', result)

    @with_context
    @patch('pybossa.auth.current_user', new=mock_admin)
    def test_admin_user_can_read_result(self):
        """Test admin users can read results"""

        result = self.create_result()

        assert ensure_authorized_to('read', result)

    @with_context
    @patch('pybossa.auth.current_user', new=mock_anonymous)
    def test_anonymous_user_cannot_save_results(self):
        """Test anonymous users cannot save results of a specific project"""

        result = Result()

        assert_raises(Unauthorized, ensure_authorized_to, 'create', result)

    @with_context
    @patch('pybossa.auth.current_user', new=mock_authenticated)
    def test_authenticated_user_cannot_save_results(self):
        """Test authenticated users cannot save results of a specific project"""

        result = Result()

        assert_raises(Forbidden, ensure_authorized_to, 'create', result)

    @with_context
    @patch('pybossa.auth.current_user', new=mock_admin)
    def test_admin_user_cannot_save_results(self):
        """Test admin users cannot save results of a specific project"""

        result = Result()

        assert_raises(Forbidden, ensure_authorized_to, 'create', result)

    @with_context
    @patch('pybossa.auth.current_user', new=mock_anonymous)
    def test_anonymous_user_cannot_delete_results(self):
        """Test anonymous users cannot delete results of a specific project"""

        result = Result()

        assert_raises(Unauthorized, ensure_authorized_to, 'delete', result)

    @with_context
    @patch('pybossa.auth.current_user', new=mock_authenticated)
    def test_authenticated_user_cannot_delete_results(self):
        """Test authenticated users cannot delete results of a specific project"""

        result = Result()

        assert_raises(Forbidden, ensure_authorized_to, 'delete', result)

    @with_context
    @patch('pybossa.auth.current_user', new=mock_admin)
    def test_admin_user_cannot_delete_results(self):
        """Test admin users cannot delete results of a specific project"""

        result = Result()

        assert_raises(Forbidden, ensure_authorized_to, 'delete', result)

    @with_context
    @patch('pybossa.auth.current_user', new=mock_anonymous)
    def test_anonymous_user_cannot_update_results(self):
        """Test anonymous users cannot update results of a specific project"""

        result = self.create_result()

        assert_raises(Unauthorized, ensure_authorized_to, 'update', result)

    @with_context
    @patch('pybossa.auth.current_user', new=mock_authenticated)
    def test_auth_user_cannot_update_results(self):
        """Test auth users but not owner cannot update results of a specific project"""

        result = self.create_result()

        assert_raises(Forbidden, ensure_authorized_to, 'update', result)

    @with_context
    @patch('pybossa.auth.current_user', new=mock_owner)
    def test_auth_owner_can_update_results(self):
        """Test auth owner can update results of a specific project"""

        result = self.create_result()
        result.info = dict(new='value')

        assert ensure_authorized_to('update', result)

        updated_result = self.result_repo.get_by(id=result.id)

        err_msg = "The result has not been updated"
        assert updated_result.info['new'] == 'value', err_msg
コード例 #8
0
ファイル: test_result.py プロジェクト: PyBossa/pybossa
class TestResultAPI(TestAPI):
    def setUp(self):
        super(TestResultAPI, self).setUp()
        self.result_repo = ResultRepository(db)

    def create_result(self, n_results=1, n_answers=1, owner=None, filter_by=False):
        if owner:
            owner = owner
        else:
            owner = UserFactory.create()
        project = ProjectFactory.create(owner=owner)
        tasks = []
        for i in range(n_results):
            tasks.append(TaskFactory.create(n_answers=n_answers, project=project))
        for i in range(n_answers):
            for task in tasks:
                TaskRunFactory.create(task=task, project=project)
        if filter_by:
            return self.result_repo.filter_by(project_id=1)
        else:
            return self.result_repo.get_by(project_id=1)

    @with_context
    def test_result_query_without_params(self):
        """ Test API Result query"""
        result = self.create_result(n_answers=10)
        res = self.app.get("/api/result")
        results = json.loads(res.data)
        assert len(results) == 1, results
        result = results[0]
        assert result["info"] is None, result
        assert len(result["task_run_ids"]) == 10, result
        assert result["task_run_ids"] == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], result
        assert result["project_id"] == 1, result
        assert result["task_id"] == 1, result
        assert result["created"] is not None, result

        result = self.create_result(n_answers=10)
        result = result_repo.get(2)
        result.created = "2119-01-01T14:37:30.642119"
        result_repo.update(result)

        url = "/api/result?desc=true"
        res = self.app.get(url)
        data = json.loads(res.data)
        err_msg = "It should get the last item first."
        assert data[0]["created"] == "2119-01-01T14:37:30.642119", err_msg

        url = "/api/result"
        res = self.app.get(url)
        data = json.loads(res.data)
        err_msg = "It should get not the last item first."
        assert data[0]["created"] != "2119-01-01T14:37:30.642119", err_msg

        # The output should have a mime-type: application/json
        assert res.mimetype == "application/json", res

    @with_context
    def test_result_query_without_params_with_context(self):
        """ Test API Result query with context."""
        result = self.create_result(n_answers=10)
        res = self.app.get("/api/result")
        results = json.loads(res.data)
        assert len(results) == 1, results
        result = results[0]
        assert result["info"] is None, result
        assert len(result["task_run_ids"]) == 10, result
        assert result["task_run_ids"] == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], result
        assert result["project_id"] == 1, result
        assert result["task_id"] == 1, result
        assert result["created"] is not None, result

        # The output should have a mime-type: application/json
        assert res.mimetype == "application/json", res

    @with_context
    def test_result_query_with_params(self):
        """Test API query for result with params works"""
        owner = UserFactory.create()
        results = self.create_result(n_results=10, filter_by=True, owner=owner)
        # Test for real field
        res = self.app.get("/api/result?api_key=" + owner.api_key)
        data = json.loads(res.data)
        # Should return one result
        assert len(data) == 10, data
        # Correct result
        assert data[0]["project_id"] == 1, data
        res = self.app.get("/api/project?api_key=" + owner.api_key)
        project = json.loads(res.data)
        assert len(project) == 1, project
        assert project[0]["owner_id"] == owner.id, project

        owner_two = UserFactory.create()
        res = self.app.get("/api/result?api_key=" + owner_two.api_key)
        data = json.loads(res.data)
        # Should return zero results
        assert len(data) == 0, data

        owner_two = UserFactory.create()
        res = self.app.get("/api/result?all=1&api_key=" + owner_two.api_key)
        data = json.loads(res.data)
        # Should return ten results
        assert len(data) == 10, data
        assert data[0]["project_id"] == 1, data

        # Valid field but wrong value
        url = "/api/result?project_id=99999999&api_key=" + owner.api_key
        res = self.app.get(url)
        data = json.loads(res.data)
        assert len(data) == 0, data

        # Multiple fields
        url = "/api/result?project_id=1&task_id=1&api_key=" + owner.api_key
        res = self.app.get(url)
        data = json.loads(res.data)
        # One result
        assert len(data) == 1, data
        # Correct result
        assert data[0]["project_id"] == 1, data
        assert data[0]["task_id"] == 1, data

        # Multiple fields
        url = "/api/result?project_id=1&task_id=1&api_key=" + owner_two.api_key
        res = self.app.get(url)
        data = json.loads(res.data)
        # Zero result
        assert len(data) == 0, data
        url = "/api/result?all=1&project_id=1&task_id=1&api_key=" + owner_two.api_key
        res = self.app.get(url)
        data = json.loads(res.data)
        # One result
        assert len(data) == 1, data
        # Correct result
        assert data[0]["project_id"] == 1, data
        assert data[0]["task_id"] == 1, data

        # Limits
        url = "/api/result?project_id=1&limit=5&api_key=" + owner.api_key
        res = self.app.get(url)
        data = json.loads(res.data)
        for item in data:
            assert item["project_id"] == 1, item
        assert len(data) == 5, len(data)

        # Limits
        url = "/api/result?project_id=1&limit=5&api_key=" + owner_two.api_key
        res = self.app.get(url)
        data = json.loads(res.data)
        assert len(data) == 0, data

        # Limits
        url = "/api/result?all=1&project_id=1&limit=5&api_key=" + owner_two.api_key
        res = self.app.get(url)
        data = json.loads(res.data)
        for item in data:
            assert item["project_id"] == 1, item
        assert len(data) == 5, len(data)

        # Keyset pagination
        url = "/api/result?project_id=1&limit=5&last_id=1&api_key=" + owner.api_key
        res = self.app.get(url)
        data = json.loads(res.data)
        for item in data:
            assert item["project_id"] == 1, item
        assert len(data) == 5, data
        assert data[0]["id"] == 2, data[0]

        # Keyset pagination
        url = "/api/result?project_id=1&limit=5&last_id=1&api_key=" + owner_two.api_key
        res = self.app.get(url)
        data = json.loads(res.data)
        assert len(data) == 0, data

        # Keyset pagination
        url = "/api/result?all=1&project_id=1&limit=5&last_id=1&api_key=" + owner.api_key
        res = self.app.get(url)
        data = json.loads(res.data)
        for item in data:
            assert item["project_id"] == 1, item
        assert len(data) == 5, data
        assert data[0]["id"] == 2, data[0]

    @with_context
    def test_result_post(self):
        """Test API Result creation"""
        admin = UserFactory.create()
        user = UserFactory.create()
        non_owner = UserFactory.create()
        project = ProjectFactory.create(owner=user)
        data = dict(info="final result")

        # anonymous user
        # no api-key
        res = self.app.post("/api/result", data=json.dumps(data))
        error_msg = "Should not be allowed to create"
        assert_equal(res.status, "401 UNAUTHORIZED", error_msg)

        ### real user but not allowed as not owner!
        res = self.app.post("/api/result?api_key=" + non_owner.api_key, data=json.dumps(data))

        error_msg = "Should not be able to post tasks for projects of others"
        assert_equal(res.status, "403 FORBIDDEN", error_msg)

        # now a real user
        res = self.app.post("/api/result?api_key=" + user.api_key, data=json.dumps(data))
        assert_equal(res.status, "403 FORBIDDEN", error_msg)

        # now the root user
        res = self.app.post("/api/result?api_key=" + admin.api_key, data=json.dumps(data))
        assert_equal(res.status, "403 FORBIDDEN", error_msg)

        # POST with not JSON data
        url = "/api/result?api_key=%s" % user.api_key
        res = self.app.post(url, data=data)
        err = json.loads(res.data)
        assert res.status_code == 415, err
        assert err["status"] == "failed", err
        assert err["target"] == "result", err
        assert err["action"] == "POST", err
        assert err["exception_cls"] == "ValueError", err

        # POST with not allowed args
        res = self.app.post(url + "&foo=bar", data=json.dumps(data))
        err = json.loads(res.data)
        assert res.status_code == 415, err
        assert err["status"] == "failed", err
        assert err["target"] == "result", err
        assert err["action"] == "POST", err
        assert err["exception_cls"] == "AttributeError", err

        # POST with fake data
        data["wrongfield"] = 13
        res = self.app.post(url, data=json.dumps(data))
        err = json.loads(res.data)
        assert res.status_code == 415, err
        assert err["status"] == "failed", err
        assert err["target"] == "result", err
        assert err["action"] == "POST", err
        assert err["exception_cls"] == "TypeError", err

    def test_result_post_with_reserved_fields_returns_error(self):
        user = UserFactory.create()
        project = ProjectFactory.create(owner=user)
        data = {"created": "today", "id": 222, "project_id": project.id}

        res = self.app.post("/api/result?api_key=" + user.api_key, data=json.dumps(data))

        assert res.status_code == 400, res.status_code
        error = json.loads(res.data)
        assert error["exception_msg"] == "Reserved keys in payload", error

    def test_result_put_with_reserved_fields_returns_error(self):
        user = UserFactory.create()
        result = self.create_result(owner=user)
        print result
        url = "/api/result/%s?api_key=%s" % (result.id, user.api_key)
        data = {"created": "today", "project_id": 1, "id": 222}

        res = self.app.put(url, data=json.dumps(data))

        assert res.status_code == 400, res.status_code
        error = json.loads(res.data)
        assert error["exception_msg"] == "Reserved keys in payload", error

    @with_context
    def test_result_update(self):
        """Test API result update"""
        admin = UserFactory.create()
        user = UserFactory.create()
        non_owner = UserFactory.create()
        data = dict(info=dict(foo="bar"))
        datajson = json.dumps(data)
        result = self.create_result(owner=user)

        ## anonymous
        res = self.app.put("/api/result/%s" % result.id, data=datajson)
        assert_equal(res.status, "401 UNAUTHORIZED", res.status)
        ### real user but not allowed as not owner!
        url = "/api/result/%s?api_key=%s" % (result.id, non_owner.api_key)
        res = self.app.put(url, data=datajson)
        assert_equal(res.status, "403 FORBIDDEN", res.status)

        ### real user
        url = "/api/result/%s?api_key=%s" % (result.id, user.api_key)
        res = self.app.put(url, data=datajson)
        out = json.loads(res.data)
        assert_equal(res.status, "200 OK", res.data)
        assert_equal(result.info["foo"], data["info"]["foo"])
        assert result.id == out["id"], out

        ### root
        res = self.app.put("/api/result/%s?api_key=%s" % (result.id, admin.api_key), data=datajson)
        assert_equal(res.status, "403 FORBIDDEN", res.status)

        # PUT with not JSON data
        res = self.app.put(url, data=None)
        err = json.loads(res.data)
        assert res.status_code == 415, err
        assert err["status"] == "failed", err
        assert err["target"] == "result", err
        assert err["action"] == "PUT", err
        assert err["exception_cls"] == "ValueError", err

        # PUT with not allowed args
        res = self.app.put(url + "&foo=bar", data=json.dumps(data))
        err = json.loads(res.data)
        assert res.status_code == 415, err
        assert err["status"] == "failed", err
        assert err["target"] == "result", err
        assert err["action"] == "PUT", err
        assert err["exception_cls"] == "AttributeError", err

        # PUT with fake data
        data["wrongfield"] = 13
        res = self.app.put(url, data=json.dumps(data))
        err = json.loads(res.data)
        assert res.status_code == 415, err
        assert err["status"] == "failed", err
        assert err["target"] == "result", err
        assert err["action"] == "PUT", err
        assert err["exception_cls"] == "TypeError", err

    @with_context
    def test_result_delete(self):
        """Test API result delete"""
        admin = UserFactory.create()
        user = UserFactory.create()
        non_owner = UserFactory.create()
        result = self.create_result(owner=user)

        ## anonymous
        res = self.app.delete("/api/result/%s" % result.id)
        error_msg = "Anonymous should not be allowed to update"
        assert_equal(res.status, "401 UNAUTHORIZED", error_msg)

        ### real user but not allowed as not owner!
        url = "/api/result/%s?api_key=%s" % (result.id, non_owner.api_key)
        res = self.app.delete(url)
        error_msg = "Should not be able to update tasks of others"
        assert_equal(res.status, "403 FORBIDDEN", error_msg)

        #### real user
        # DELETE with not allowed args
        res = self.app.delete(url + "&foo=bar")
        err = json.loads(res.data)
        assert res.status_code == 415, err
        assert err["status"] == "failed", err
        assert err["target"] == "result", err
        assert err["action"] == "DELETE", err
        assert err["exception_cls"] == "AttributeError", err

        # DELETE returns 403
        url = "/api/result/%s?api_key=%s" % (result.id, user.api_key)
        res = self.app.delete(url)
        assert_equal(res.status, "403 FORBIDDEN", res.data)

        #### root user
        url = "/api/result/%s?api_key=%s" % (result.id, admin.api_key)
        res = self.app.delete(url)
        assert_equal(res.status, "403 FORBIDDEN", res.data)

    @with_context
    def test_get_last_version(self):
        """Test API result returns always latest version."""
        result = self.create_result()
        project = project_repo.get(result.project_id)
        task = task_repo.get_task(result.task_id)
        task.n_answers = 2
        TaskRunFactory.create(task=task, project=project)
        result = result_repo.get_by(project_id=project.id)

        assert result.last_version is True, result.last_version

        result_id = result.id

        results = result_repo.filter_by(project_id=project.id, last_version=False)
        assert len(results) == 2, len(results)

        for r in results:
            if r.id == result_id:
                assert r.last_version is True, r.last_version
            else:
                assert r.last_version is False, r.last_version
コード例 #9
0
 def setUp(self):
     super(TestResultAuthorization, self).setUp()
     self.result_repo = ResultRepository(db)
コード例 #10
0
class TestIIIFAnnotationAnalyst(Test):
    def setUp(self):
        super(TestIIIFAnnotationAnalyst, self).setUp()
        self.ctx = ContextFixtures()
        self.result_repo = ResultRepository(db)
        self.task_repo = TaskRepository(db)
        self.iiif_analyst = IIIFAnnotationAnalyst()
        self.comments = ['Some comment']
        self.tags = {
            'foo': [
                dict(x=100, y=100, w=100, h=100),
                dict(x=200, y=200, w=200, h=200)
            ],
            'bar': [dict(x=300, y=300, w=300, h=300)]
        }
        transcription_data = {'foo': ['bar', 'baz'], 'qux': ['quux', 'quuz']}
        self.transcriptions_df = pandas.DataFrame(transcription_data)

        self.comment_annos = []
        for comment in self.comments:
            self.comment_annos.append({
                'motivation': 'commenting',
                'body': {
                    'type': 'TextualBody',
                    'value': comment,
                    'purpose': 'commenting',
                    'format': 'text/plain'
                },
                'target': 'example.com'
            })

        self.tagging_annos = []
        for tag, rect_list in self.tags.items():
            for rect in rect_list:
                self.tagging_annos.append({
                    'motivation': 'tagging',
                    'body': {
                        'type': 'TextualBody',
                        'purpose': 'tagging',
                        'value': tag
                    },
                    'target': {
                        'source': 'example.com',
                        'selector': {
                            'conformsTo':
                            'http://www.w3.org/TR/media-frags/',
                            'type':
                            'FragmentSelector',
                            'value':
                            '?xywh={0},{1},{2},{3}'.format(
                                rect['x'], rect['y'], rect['w'], rect['h'])
                        }
                    }
                })

        self.transcription_annos = []
        for tag, value_list in transcription_data.items():
            for value in value_list:
                self.transcription_annos.append({
                    'motivation':
                    'describing',
                    'body': [{
                        'type': 'TextualBody',
                        'purpose': 'tagging',
                        'value': tag
                    }, {
                        'type': 'TextualBody',
                        'purpose': 'describing',
                        'value': value,
                        'format': 'text/plain'
                    }],
                    'target':
                    'example.com'
                })

        self.data = {
            'user_id': [1, 2, 3],
            'info':
            [self.comment_annos, self.tagging_annos, self.transcription_annos]
        }

    def test_get_comments(self):
        """Test IIIF Annotation comments are returned."""
        task_run_df = pandas.DataFrame(self.data)
        comments = self.iiif_analyst.get_comments(task_run_df)
        expected = [(1, comment) for comment in self.comments]
        assert_equal(comments, expected)

    def test_get_tags(self):
        """Test IIIF Annotation tags are returned."""
        task_run_df = pandas.DataFrame(self.data)
        tags = self.iiif_analyst.get_tags(task_run_df)
        assert_dict_equal(tags, self.tags)

    def test_get_tags_with_body_list(self):
        """Test IIIF Annotation tags are returned when body is a list."""
        self.tagging_annos[0]['body'] = [
            self.tagging_annos[0]['body'], {
                'type': 'TextualBody',
                'purpose': 'classifying',
                'value': 'foo'
            }
        ]
        task_run_df = pandas.DataFrame(self.data)
        tags = self.iiif_analyst.get_tags(task_run_df)
        assert_dict_equal(tags, self.tags)

    def test_get_transcriptions_df(self):
        """Test IIIF Annotation transcriptions are returned."""
        task_run_df = pandas.DataFrame(self.data)
        df = self.iiif_analyst.get_transcriptions_df(task_run_df)
        assert_dict_equal(df.to_dict(), self.transcriptions_df.to_dict())

    @with_context
    @patch('pybossa_lc.model.base.wa_client')
    def test_analysis_with_no_transcriptions(self, mock_client):
        """Test IIIF analysis with no transcriptions."""
        n_answers = 3
        target = 'example.com'
        task = self.ctx.create_task(n_answers, target)
        TaskRunFactory.create_batch(n_answers,
                                    task=task,
                                    info=[{
                                        'motivation':
                                        'describing',
                                        'body': [{
                                            'purpose': 'describing',
                                            'value': ''
                                        }, {
                                            'purpose': 'tagging',
                                            'value': 'foo'
                                        }]
                                    }])
        result = self.result_repo.filter_by(project_id=task.project_id)[0]
        fake_search = MagicMock()
        fake_search.return_value = []
        mock_client.search_annotations = fake_search
        self.iiif_analyst.analyse(result.id)
        assert_equal(mock_client.create_annotation.called, False)

    @with_context
    @patch('pybossa_lc.model.base.wa_client')
    def test_fragment_selector_stripped(self, mock_client):
        """Test IIIF fragment selector is stripped if rule applied."""
        n_answers = 3
        source = 'example.com'
        target = {
            'source': source,
            'selector': {
                'conformsTo': 'http://www.w3.org/TR/media-frags/',
                'type': 'FragmentSelector',
                'value': '?xywh=100,100,100,100'
            }
        }
        rules = dict(remove_fragment_selector=True)
        anno_collection = 'http://eg.com/collection'
        task = self.ctx.create_task(n_answers,
                                    target,
                                    rules=rules,
                                    anno_collection=anno_collection)
        tag = 'foo'
        value = 'bar'
        TaskRunFactory.create_batch(n_answers,
                                    task=task,
                                    info=[{
                                        'motivation':
                                        'describing',
                                        'body': [{
                                            'purpose': 'describing',
                                            'value': value
                                        }, {
                                            'purpose': 'tagging',
                                            'value': 'foo'
                                        }]
                                    }])
        result = self.result_repo.filter_by(project_id=task.project_id)[0]
        fake_search = MagicMock()
        fake_search.return_value = []
        mock_client.search_annotations = fake_search
        self.iiif_analyst.analyse(result.id)
        func = mock_client.create_annotation
        func.assert_called_once_with(
            anno_collection, {
                'motivation':
                'describing',
                'type':
                'Annotation',
                'generator':
                [{
                    "id": flask_app.config.get('GITHUB_REPO'),
                    "type": "Software",
                    "name": "LibCrowds",
                    "homepage": flask_app.config.get('SPA_SERVER_NAME')
                }, {
                    "id": url_for('api.api_task', oid=task.id),
                    "type": "Software"
                }],
                'body': [{
                    'type': 'TextualBody',
                    'purpose': 'describing',
                    'value': value,
                    'format': 'text/plain'
                }, {
                    'type': 'TextualBody',
                    'purpose': 'tagging',
                    'value': tag
                }],
                'target':
                source
            })

    @with_context
    @patch('pybossa_lc.model.base.wa_client')
    def test_comment_annotation_created(self, mock_client):
        """Test IIIF comment annotations are created."""
        n_answers = 1
        target = 'example.com'
        anno_collection = 'http://eg.com/collection'
        task = self.ctx.create_task(n_answers,
                                    target,
                                    anno_collection=anno_collection)
        user = UserFactory()
        value = 'foo'
        TaskRunFactory.create_batch(n_answers,
                                    user=user,
                                    task=task,
                                    info=[{
                                        'motivation': 'commenting',
                                        'body': {
                                            'value': value
                                        }
                                    }])
        result = self.result_repo.filter_by(project_id=task.project_id)[0]
        fake_search = MagicMock()
        fake_search.return_value = []
        mock_client.search_annotations = fake_search
        self.iiif_analyst.analyse(result.id)
        func = mock_client.create_annotation
        func.assert_called_once_with(
            anno_collection, {
                'motivation':
                'commenting',
                'type':
                'Annotation',
                'creator': {
                    'id': url_for('api.api_user', oid=user.id),
                    'type': 'Person',
                    'name': user.fullname,
                    'nickname': user.name
                },
                'generator':
                [{
                    "id": flask_app.config.get('GITHUB_REPO'),
                    "type": "Software",
                    "name": "LibCrowds",
                    "homepage": flask_app.config.get('SPA_SERVER_NAME')
                }, {
                    "id": url_for('api.api_task', oid=task.id),
                    "type": "Software"
                }],
                'body': {
                    'type': 'TextualBody',
                    'purpose': 'commenting',
                    'value': value,
                    'format': 'text/plain'
                },
                'target':
                target
            })

    @with_context
    @patch('pybossa_lc.model.base.wa_client')
    def test_transcriptions_are_normalised(self, mock_client):
        """Test IIIF transcriptions are normalised according to set rules."""
        n_answers = 1
        target = 'example.com'
        anno_collection = 'http://eg.com/collection'
        rules = dict(case='title',
                     whitespace='full_stop',
                     trim_punctuation=True)
        task = self.ctx.create_task(n_answers,
                                    target,
                                    rules=rules,
                                    anno_collection=anno_collection)
        tag = 'foo'
        values = ['HeLLo!', ' hello ', ' hELLO.']
        for value in values:
            TaskRunFactory.create(task=task,
                                  info=[{
                                      'motivation':
                                      'describing',
                                      'body': [{
                                          'purpose': 'describing',
                                          'value': value
                                      }, {
                                          'purpose': 'tagging',
                                          'value': tag
                                      }]
                                  }])
        result = self.result_repo.filter_by(project_id=task.project_id)[0]
        fake_search = MagicMock()
        fake_search.return_value = []
        mock_client.search_annotations = fake_search
        self.iiif_analyst.analyse(result.id)
        func = mock_client.create_annotation
        func.assert_called_once_with(
            anno_collection, {
                'motivation':
                'describing',
                'type':
                'Annotation',
                'generator':
                [{
                    "id": flask_app.config.get('GITHUB_REPO'),
                    "type": "Software",
                    "name": "LibCrowds",
                    "homepage": flask_app.config.get('SPA_SERVER_NAME')
                }, {
                    "id": url_for('api.api_task', oid=task.id),
                    "type": "Software"
                }],
                'body': [{
                    'type': 'TextualBody',
                    'purpose': 'describing',
                    'value': 'Hello',
                    'format': 'text/plain'
                }, {
                    'type': 'TextualBody',
                    'purpose': 'tagging',
                    'value': tag
                }],
                'target':
                target
            })

    @with_context
    @patch('pybossa_lc.model.base.wa_client')
    def test_with_matching_transcriptions(self, mock_client):
        """Test IIIF results with matching transcriptions."""
        n_answers = 3
        target = 'example.com'
        anno_collection = 'http://eg.com/collection'
        task = self.ctx.create_task(n_answers,
                                    target,
                                    rules={},
                                    anno_collection=anno_collection)
        value = 'foo'
        tag = 'bar'
        TaskRunFactory.create_batch(n_answers,
                                    task=task,
                                    info=[{
                                        'motivation':
                                        'describing',
                                        'body': [{
                                            'purpose': 'describing',
                                            'value': value
                                        }, {
                                            'purpose': 'tagging',
                                            'value': tag
                                        }]
                                    }])
        result = self.result_repo.filter_by(project_id=task.project_id)[0]
        fake_search = MagicMock()
        fake_search.return_value = []
        mock_client.search_annotations = fake_search
        self.iiif_analyst.analyse(result.id)
        func = mock_client.create_annotation
        func.assert_called_once_with(
            anno_collection, {
                'motivation':
                'describing',
                'type':
                'Annotation',
                'generator':
                [{
                    "id": flask_app.config.get('GITHUB_REPO'),
                    "type": "Software",
                    "name": "LibCrowds",
                    "homepage": flask_app.config.get('SPA_SERVER_NAME')
                }, {
                    "id": url_for('api.api_task', oid=task.id),
                    "type": "Software"
                }],
                'body': [{
                    'type': 'TextualBody',
                    'purpose': 'describing',
                    'value': value,
                    'format': 'text/plain'
                }, {
                    'type': 'TextualBody',
                    'purpose': 'tagging',
                    'value': tag
                }],
                'target':
                target
            })

    @with_context
    @patch('pybossa_lc.model.base.wa_client')
    def test_redundancy_increased_when_not_max(self, mock_client):
        """Test IIIF task redundancy is updated when max not reached."""
        n_answers = 3
        target = 'example.com'
        task = self.ctx.create_task(n_answers,
                                    target,
                                    max_answers=n_answers + 1)
        for i in range(n_answers):
            TaskRunFactory.create(task=task,
                                  info=[{
                                      'motivation':
                                      'describing',
                                      'body': [{
                                          'purpose': 'describing',
                                          'value': i
                                      }, {
                                          'purpose': 'tagging',
                                          'value': i
                                      }]
                                  }])
        result = self.result_repo.filter_by(project_id=task.project_id)[0]
        fake_search = MagicMock()
        fake_search.return_value = []
        mock_client.search_annotations = fake_search
        self.iiif_analyst.analyse(result.id)
        assert_equal(mock_client.create_annotation.called, False)

        updated_task = self.task_repo.get_task(task.id)
        assert_equal(updated_task.n_answers, n_answers + 1)

    @with_context
    @patch('pybossa_lc.model.base.wa_client')
    def test_redundancy_not_increased_when_max(self, mock_client):
        """Test IIIF task redundancy is not updated when max is reached."""
        n_answers = 3
        target = 'example.com'
        task = self.ctx.create_task(n_answers, target, max_answers=n_answers)
        for i in range(n_answers):
            TaskRunFactory.create(task=task,
                                  info=[{
                                      'motivation':
                                      'describing',
                                      'body': [{
                                          'purpose': 'describing',
                                          'value': i
                                      }, {
                                          'purpose': 'tagging',
                                          'value': i
                                      }]
                                  }])
        result = self.result_repo.filter_by(project_id=task.project_id)[0]
        fake_search = MagicMock()
        fake_search.return_value = []
        mock_client.search_annotations = fake_search
        self.iiif_analyst.analyse(result.id)
        assert_equal(mock_client.create_annotation.called, False)

        updated_task = self.task_repo.get_task(task.id)
        assert_equal(updated_task.n_answers, n_answers)

    @with_context
    @patch('pybossa_lc.model.base.wa_client')
    def test_redundancy_not_increased_for_tags(self, mock_client):
        """Test IIIF task redundancy is not updated for tags."""
        n_answers = 3
        target = 'example.com'
        task = self.ctx.create_task(n_answers,
                                    target,
                                    max_answers=n_answers + 1)
        for i in range(n_answers):
            TaskRunFactory.create(
                task=task,
                info=[{
                    'motivation': 'tagging',
                    'body': {
                        'type': 'TextualBody',
                        'purpose': 'tagging',
                        'value': 'foo'
                    },
                    'target': {
                        'source': 'example.com',
                        'selector': {
                            'conformsTo': 'http://www.w3.org/TR/media-frags/',
                            'type': 'FragmentSelector',
                            'value': '?xywh={0},{0},{0},{0}'.format(i)
                        }
                    }
                }])
        result = self.result_repo.filter_by(project_id=task.project_id)[0]
        fake_search = MagicMock()
        fake_search.return_value = []
        mock_client.search_annotations = fake_search
        self.iiif_analyst.analyse(result.id)
        updated_task = self.task_repo.get_task(task.id)
        assert_equal(updated_task.n_answers, n_answers)

    @with_context
    @patch('pybossa_lc.model.base.wa_client')
    def test_redundancy_not_increased_for_comments(self, mock_client):
        """Test IIIF task redundancy is not updated for comments."""
        n_answers = 3
        target = 'example.com'
        task = self.ctx.create_task(n_answers,
                                    target,
                                    max_answers=n_answers + 1)
        for i in range(n_answers):
            TaskRunFactory.create(task=task,
                                  info=[{
                                      'motivation': 'commenting',
                                      'body': {
                                          'type': 'TextualBody',
                                          'value': i,
                                          'purpose': 'commenting',
                                          'format': 'text/plain'
                                      }
                                  }])
        result = self.result_repo.filter_by(project_id=task.project_id)[0]
        fake_search = MagicMock()
        fake_search.return_value = []
        mock_client.search_annotations = fake_search
        self.iiif_analyst.analyse(result.id)

        updated_task = self.task_repo.get_task(task.id)
        assert_equal(updated_task.n_answers, n_answers)

    @with_context
    @patch('pybossa_lc.model.base.wa_client')
    def test_redundancy_not_increased_when_no_values(self, mock_client):
        """Test IIIF task redundancy is not updated when no values."""
        n_answers = 3
        target = 'example.com'
        task = self.ctx.create_task(n_answers,
                                    target,
                                    max_answers=n_answers + 1)
        for i in range(n_answers):
            TaskRunFactory.create(task=task,
                                  info=[{
                                      'motivation':
                                      'describing',
                                      'body': [{
                                          'purpose': 'describing',
                                          'value': ''
                                      }, {
                                          'purpose': 'tagging',
                                          'value': 'foo'
                                      }]
                                  }])
        result = self.result_repo.filter_by(project_id=task.project_id)[0]
        fake_search = MagicMock()
        fake_search.return_value = []
        mock_client.search_annotations = fake_search
        self.iiif_analyst.analyse(result.id)
        updated_task = self.task_repo.get_task(task.id)
        assert_equal(updated_task.n_answers, n_answers)
        assert_equal(mock_client.create_annotation.called, False)

    @with_context
    @patch('pybossa_lc.model.base.wa_client')
    def test_equal_regions_combined(self, mock_client):
        """Test IIIF equal tag regions are combined."""
        n_answers = 3
        target = 'example.com'
        anno_collection = 'http://eg.com/collection'
        task = self.ctx.create_task(n_answers,
                                    target,
                                    rules={},
                                    anno_collection=anno_collection)
        rect = dict(x=400, y=200, w=100, h=150)
        tag = 'foo'
        TaskRunFactory.create_batch(
            n_answers,
            task=task,
            info=[{
                'motivation': 'tagging',
                'body': {
                    'type': 'TextualBody',
                    'purpose': 'tagging',
                    'value': tag
                },
                'target': {
                    'source': 'example.com',
                    'selector': {
                        'conformsTo':
                        'http://www.w3.org/TR/media-frags/',
                        'type':
                        'FragmentSelector',
                        'value':
                        '?xywh={0},{1},{2},{3}'.format(rect['x'], rect['y'],
                                                       rect['w'], rect['h'])
                    }
                }
            }])
        result = self.result_repo.filter_by(project_id=task.project_id)[0]
        fake_search = MagicMock()
        fake_search.return_value = []
        mock_client.search_annotations = fake_search
        self.iiif_analyst.analyse(result.id)
        func = mock_client.create_annotation
        func.assert_called_once_with(
            anno_collection, {
                'motivation':
                'tagging',
                'type':
                'Annotation',
                'generator':
                [{
                    "id": flask_app.config.get('GITHUB_REPO'),
                    "type": "Software",
                    "name": "LibCrowds",
                    "homepage": flask_app.config.get('SPA_SERVER_NAME')
                }, {
                    "id": url_for('api.api_task', oid=task.id),
                    "type": "Software"
                }],
                'body': {
                    'type': 'TextualBody',
                    'purpose': 'tagging',
                    'value': tag
                },
                'target': {
                    'source': 'example.com',
                    'selector': {
                        'conformsTo':
                        'http://www.w3.org/TR/media-frags/',
                        'type':
                        'FragmentSelector',
                        'value':
                        '?xywh={0},{1},{2},{3}'.format(rect['x'], rect['y'],
                                                       rect['w'], rect['h'])
                    }
                }
            })

    @with_context
    @patch('pybossa_lc.model.base.wa_client')
    def test_equal_regions_combined(self, mock_client):
        """Test IIIF equal tag regions are combined."""
        n_answers = 3
        target = 'example.com'
        anno_collection = 'http://eg.com/collection'
        task = self.ctx.create_task(n_answers,
                                    target,
                                    rules={},
                                    anno_collection=anno_collection)
        rect = dict(x=400, y=200, w=100, h=150)
        tag = 'foo'
        TaskRunFactory.create_batch(
            n_answers,
            task=task,
            info=[{
                'motivation': 'tagging',
                'body': {
                    'type': 'TextualBody',
                    'purpose': 'tagging',
                    'value': tag
                },
                'target': {
                    'source': 'example.com',
                    'selector': {
                        'conformsTo':
                        'http://www.w3.org/TR/media-frags/',
                        'type':
                        'FragmentSelector',
                        'value':
                        '?xywh={0},{1},{2},{3}'.format(rect['x'], rect['y'],
                                                       rect['w'], rect['h'])
                    }
                }
            }])
        result = self.result_repo.filter_by(project_id=task.project_id)[0]
        fake_search = MagicMock()
        fake_search.return_value = []
        mock_client.search_annotations = fake_search
        self.iiif_analyst.analyse(result.id)
        func = mock_client.create_annotation
        func.assert_called_once_with(
            anno_collection, {
                'motivation':
                'tagging',
                'type':
                'Annotation',
                'generator':
                [{
                    "id": flask_app.config.get('GITHUB_REPO'),
                    "type": "Software",
                    "name": "LibCrowds",
                    "homepage": flask_app.config.get('SPA_SERVER_NAME')
                }, {
                    "id": url_for('api.api_task', oid=task.id),
                    "type": "Software"
                }],
                'body': {
                    'type': 'TextualBody',
                    'purpose': 'tagging',
                    'value': tag
                },
                'target': {
                    'source': 'example.com',
                    'selector': {
                        'conformsTo':
                        'http://www.w3.org/TR/media-frags/',
                        'type':
                        'FragmentSelector',
                        'value':
                        '?xywh={0},{1},{2},{3}'.format(rect['x'], rect['y'],
                                                       rect['w'], rect['h'])
                    }
                }
            })

    @with_context
    @patch('pybossa_lc.model.base.wa_client')
    def test_similar_regions_combined(self, mock_client):
        """Test IIIF similar tag regions are combined."""
        n_answers = 3
        target = 'example.com'
        anno_collection = 'http://eg.com/collection'
        task = self.ctx.create_task(n_answers,
                                    target,
                                    rules={},
                                    anno_collection=anno_collection)
        rect1 = dict(x=90, y=100, w=110, h=90)
        rect2 = dict(x=100, y=110, w=90, h=100)
        rect3 = dict(x=110, y=90, w=100, h=110)
        rects = [rect1, rect2, rect3]
        tag = 'foo'
        for rect in rects:
            TaskRunFactory.create(
                task=task,
                info=[{
                    'motivation': 'tagging',
                    'body': {
                        'type': 'TextualBody',
                        'purpose': 'tagging',
                        'value': tag
                    },
                    'target': {
                        'source': 'example.com',
                        'selector': {
                            'conformsTo':
                            'http://www.w3.org/TR/media-frags/',
                            'type':
                            'FragmentSelector',
                            'value':
                            '?xywh={0},{1},{2},{3}'.format(
                                rect['x'], rect['y'], rect['w'], rect['h'])
                        }
                    }
                }])
        result = self.result_repo.filter_by(project_id=task.project_id)[0]
        fake_search = MagicMock()
        fake_search.return_value = []
        mock_client.search_annotations = fake_search
        self.iiif_analyst.analyse(result.id)
        func = mock_client.create_annotation
        func.assert_called_once_with(
            anno_collection, {
                'motivation':
                'tagging',
                'type':
                'Annotation',
                'generator':
                [{
                    "id": flask_app.config.get('GITHUB_REPO'),
                    "type": "Software",
                    "name": "LibCrowds",
                    "homepage": flask_app.config.get('SPA_SERVER_NAME')
                }, {
                    "id": url_for('api.api_task', oid=task.id),
                    "type": "Software"
                }],
                'body': {
                    'type': 'TextualBody',
                    'purpose': 'tagging',
                    'value': tag
                },
                'target': {
                    'source': 'example.com',
                    'selector': {
                        'conformsTo': 'http://www.w3.org/TR/media-frags/',
                        'type': 'FragmentSelector',
                        'value': '?xywh=90,90,120,110'
                    }
                }
            })

    @with_context
    @patch('pybossa_lc.model.base.wa_client')
    def test_different_regions_combined(self, mock_client):
        """Test IIIF different tag regions are not combined."""
        n_answers = 3
        target = 'example.com'
        anno_collection = 'http://eg.com/collection'
        task = self.ctx.create_task(n_answers,
                                    target,
                                    rules={},
                                    anno_collection=anno_collection)
        rect1 = dict(x=10, y=10, w=10, h=10)
        rect2 = dict(x=100, y=100, w=100, h=100)
        rect3 = dict(x=200, y=200, w=200, h=200)
        rects = [rect1, rect2, rect3]
        tag = 'foo'
        for rect in rects:
            TaskRunFactory.create(
                task=task,
                info=[{
                    'motivation': 'tagging',
                    'body': {
                        'type': 'TextualBody',
                        'purpose': 'tagging',
                        'value': tag
                    },
                    'target': {
                        'source': 'example.com',
                        'selector': {
                            'conformsTo':
                            'http://www.w3.org/TR/media-frags/',
                            'type':
                            'FragmentSelector',
                            'value':
                            '?xywh={0},{1},{2},{3}'.format(
                                rect['x'], rect['y'], rect['w'], rect['h'])
                        }
                    }
                }])
        result = self.result_repo.filter_by(project_id=task.project_id)[0]
        fake_search = MagicMock()
        fake_search.return_value = []
        mock_client.search_annotations = fake_search
        self.iiif_analyst.analyse(result.id)
        assert_equal(mock_client.create_annotation.call_args_list, [
            call(
                anno_collection, {
                    'motivation':
                    'tagging',
                    'type':
                    'Annotation',
                    'generator':
                    [{
                        "id": flask_app.config.get('GITHUB_REPO'),
                        "type": "Software",
                        "name": "LibCrowds",
                        "homepage": flask_app.config.get('SPA_SERVER_NAME')
                    }, {
                        "id": url_for('api.api_task', oid=task.id),
                        "type": "Software"
                    }],
                    'body': {
                        'type': 'TextualBody',
                        'purpose': 'tagging',
                        'value': tag
                    },
                    'target': {
                        'source': 'example.com',
                        'selector': {
                            'conformsTo':
                            'http://www.w3.org/TR/media-frags/',
                            'type':
                            'FragmentSelector',
                            'value':
                            '?xywh={0},{1},{2},{3}'.format(
                                rect1['x'], rect1['y'], rect1['w'], rect1['h'])
                        }
                    }
                }),
            call(
                anno_collection, {
                    'motivation':
                    'tagging',
                    'type':
                    'Annotation',
                    'generator': [
                        {
                            "id": flask_app.config.get('GITHUB_REPO'),
                            "type": "Software",
                            "name": "LibCrowds",
                            "homepage": flask_app.config.get('SPA_SERVER_NAME')
                        }, {
                            "id": url_for('api.api_task', oid=task.id),
                            "type": "Software"
                        }
                    ],
                    'body': {
                        'type': 'TextualBody',
                        'purpose': 'tagging',
                        'value': tag
                    },
                    'target': {
                        'source': 'example.com',
                        'selector': {
                            'conformsTo':
                            'http://www.w3.org/TR/media-frags/',
                            'type':
                            'FragmentSelector',
                            'value':
                            '?xywh={0},{1},{2},{3}'.format(
                                rect2['x'], rect2['y'], rect2['w'], rect2['h'])
                        }
                    }
                }),
            call(
                anno_collection, {
                    'motivation':
                    'tagging',
                    'type':
                    'Annotation',
                    'generator': [
                        {
                            "id": flask_app.config.get('GITHUB_REPO'),
                            "type": "Software",
                            "name": "LibCrowds",
                            "homepage": flask_app.config.get('SPA_SERVER_NAME')
                        }, {
                            "id": url_for('api.api_task', oid=task.id),
                            "type": "Software"
                        }
                    ],
                    'body': {
                        'type': 'TextualBody',
                        'purpose': 'tagging',
                        'value': tag
                    },
                    'target': {
                        'source': 'example.com',
                        'selector': {
                            'conformsTo':
                            'http://www.w3.org/TR/media-frags/',
                            'type':
                            'FragmentSelector',
                            'value':
                            '?xywh={0},{1},{2},{3}'.format(
                                rect3['x'], rect3['y'], rect3['w'], rect3['h'])
                        }
                    }
                })
        ])

    @with_context
    @patch('pybossa_lc.model.base.wa_client')
    def test_old_annotations_deleted(self, mock_client):
        """Test IIIF old Annotations deleted."""
        n_answers = 3
        target = 'example.com'
        task = self.ctx.create_task(n_answers, target)
        user = UserFactory()
        TaskRunFactory.create_batch(n_answers,
                                    user=user,
                                    task=task,
                                    info=[{
                                        'motivation': 'commenting',
                                        'body': {
                                            'value': 'foo'
                                        }
                                    }])
        result = self.result_repo.filter_by(project_id=task.project_id)[0]
        fake_annos = [{'id': 'baz'}, {'id': 'qux'}]
        fake_search = MagicMock()
        fake_search.return_value = fake_annos
        mock_client.search_annotations = fake_search
        self.iiif_analyst.analyse(result.id, analyse_full=True)
        mock_client.delete_batch.assert_called_once_with(fake_annos)

    @with_context
    @patch('pybossa_lc.model.base.wa_client')
    def test_results_with_annotations_not_analysed(self, mock_client):
        """Test results with Annotations already not analysed by default."""
        n_answers = 3
        target = 'example.com'
        task = self.ctx.create_task(n_answers, target)
        user = UserFactory()
        TaskRunFactory.create_batch(n_answers,
                                    user=user,
                                    task=task,
                                    info=[{
                                        'motivation': 'commenting',
                                        'body': {
                                            'value': 'foo'
                                        }
                                    }])
        result = self.result_repo.filter_by(project_id=task.project_id)[0]
        fake_annos = [{'id': 'baz'}, {'id': 'qux'}]
        fake_search = MagicMock()
        fake_search.return_value = fake_annos
        mock_client.search_annotations = fake_search
        self.iiif_analyst.analyse(result.id)
        assert_equal(mock_client.delete_batch.called, False)

    @with_context
    @patch('pybossa_lc.model.base.wa_client')
    def test_annotation_collection_iri_added_to_result_info(self, mock_client):
        """Test IIIF result info updated with AnnotationCollection IRI."""
        n_answers = 3
        target = 'example.com'
        anno_collection = 'annotations.example.com'
        task = self.ctx.create_task(n_answers,
                                    target,
                                    anno_collection=anno_collection)
        user = UserFactory()
        TaskRunFactory.create_batch(n_answers,
                                    user=user,
                                    task=task,
                                    info=[{
                                        'motivation': 'commenting',
                                        'body': {
                                            'value': 'foo'
                                        }
                                    }])
        result = self.result_repo.filter_by(project_id=task.project_id)[0]
        fake_search = MagicMock()
        fake_search.return_value = []
        mock_client.search_annotations = fake_search
        self.iiif_analyst.analyse(result.id)
        assert_dict_equal(result.info, {'annotations': anno_collection})

    @with_context
    @patch('pybossa_lc.model.base.wa_client')
    def test_task_rejected(self, mock_client):
        """Test IIIF annotation not created when task rejected * n_answers."""
        n_answers = 3
        target = 'example.com'
        anno_collection = 'annotations.example.com'
        task = self.ctx.create_task(n_answers,
                                    target,
                                    anno_collection=anno_collection)
        user = UserFactory()
        reason = 'invalid-task'
        TaskRunFactory.create_batch(n_answers,
                                    user=user,
                                    task=task,
                                    info={'reject': reason})
        result = self.result_repo.filter_by(project_id=task.project_id)[0]
        fake_search = MagicMock()
        fake_search.return_value = []
        mock_client.search_annotations = fake_search
        self.iiif_analyst.analyse(result.id)
        assert not mock_client.create_annotation.called
        assert_dict_equal(result.info, {
            'annotations': anno_collection,
            'rejected': reason
        })
コード例 #11
0
 def setUp(self):
     super(TestResultRepository, self).setUp()
     self.result_repo = ResultRepository(db)
コード例 #12
0
ファイル: test_result.py プロジェクト: anukat2015/pybossa
 def setUp(self):
     super(TestResultAPI, self).setUp()
     self.result_repo = ResultRepository(db)
コード例 #13
0
ファイル: test_z3950.py プロジェクト: LibCrowds/pybossa-lc
class TestZ3950Analyst(Test):
    def setUp(self):
        super(TestZ3950Analyst, self).setUp()
        self.ctx = ContextFixtures()
        self.z3950_analyst = Z3950Analyst()
        self.result_repo = ResultRepository(db)
        self.task_repo = TaskRepository(db)
        self.data = {
            'user_id': [1],
            'control_number': ['123'],
            'reference': ['abc'],
            'foo': ['bar'],
            'comments': ['Some comment']
        }

    def test_get_comments(self):
        """Test Z3950 comments are returned."""
        task_run_df = pandas.DataFrame(self.data)
        comments = self.z3950_analyst.get_comments(task_run_df)
        expected = [(self.data['user_id'][i], self.data['comments'][i])
                    for i in range(len(self.data['user_id']))]
        assert_equal(comments, expected)

    def test_get_tags(self):
        """Test Z3950 tags are returned."""
        task_run_df = pandas.DataFrame(self.data)
        tags = self.z3950_analyst.get_tags(task_run_df)
        assert_dict_equal(tags, {})

    def test_get_transcriptions_df(self):
        """Test Z3950 transcriptions are returned."""
        task_run_df = pandas.DataFrame(self.data)
        df = self.z3950_analyst.get_transcriptions_df(task_run_df)
        assert_dict_equal(
            df.to_dict(), {
                'control_number': dict(enumerate(self.data['control_number'])),
                'reference': dict(enumerate(self.data['reference']))
            })

    @with_context
    @patch('pybossa_lc.model.base.wa_client')
    def test_analysis_with_no_transcriptions(self, mock_client):
        """Test Z3950 analysis with no transcriptions."""
        n_answers = 3
        target = 'example.com'
        task = self.ctx.create_task(n_answers, target)
        TaskRunFactory.create_batch(n_answers,
                                    task=task,
                                    info={
                                        'control_number': '',
                                        'reference': '',
                                        'comments': ''
                                    })
        result = self.result_repo.filter_by(project_id=task.project_id)[0]
        fake_search = MagicMock()
        fake_search.return_value = []
        mock_client.search_annotations = fake_search
        self.z3950_analyst.analyse(result.id)
        assert_equal(mock_client.create_annotation.called, False)

    @with_context
    @patch('pybossa_lc.model.base.wa_client')
    def test_analysis_with_no_transcriptions_and_old_keys(self, mock_client):
        """Test Z3950 analysis with no transcriptions and old keys."""
        n_answers = 3
        target = 'example.com'
        task = self.ctx.create_task(n_answers, target)
        TaskRunFactory.create_batch(n_answers,
                                    task=task,
                                    info={
                                        'oclc': '',
                                        'shelfmark': '',
                                        'comments': ''
                                    })
        result = self.result_repo.filter_by(project_id=task.project_id)[0]
        fake_search = MagicMock()
        fake_search.return_value = []
        mock_client.search_annotations = fake_search
        self.z3950_analyst.analyse(result.id)
        assert_equal(mock_client.create_annotation.called, False)

    @with_context
    @patch('pybossa_lc.model.base.wa_client')
    def test_analysis_with_no_reference(self, mock_client):
        """Test Z3950 analysis with no reference."""
        n_answers = 3
        target = 'example.com'
        task = self.ctx.create_task(n_answers, target)
        TaskRunFactory.create_batch(n_answers,
                                    task=task,
                                    info={
                                        'control_number': 'foo',
                                        'reference': '',
                                        'comments': ''
                                    })
        result = self.result_repo.filter_by(project_id=task.project_id)[0]
        fake_search = MagicMock()
        fake_search.return_value = []
        mock_client.search_annotations = fake_search
        self.z3950_analyst.analyse(result.id)
        assert_equal(mock_client.create_annotation.called, False)

    @with_context
    @patch('pybossa_lc.model.base.wa_client')
    def test_analysis_with_no_control_number(self, mock_client):
        """Test Z3950 analysis with no control number."""
        n_answers = 3
        target = 'example.com'
        task = self.ctx.create_task(n_answers, target)
        TaskRunFactory.create_batch(n_answers,
                                    task=task,
                                    info={
                                        'control_number': '',
                                        'reference': 'foo',
                                        'comments': ''
                                    })
        result = self.result_repo.filter_by(project_id=task.project_id)[0]
        fake_search = MagicMock()
        fake_search.return_value = []
        mock_client.search_annotations = fake_search
        self.z3950_analyst.analyse(result.id)
        assert_equal(mock_client.create_annotation.called, False)

    @with_context
    @patch('pybossa_lc.model.base.wa_client')
    def test_comment_annotation_created(self, mock_client):
        """Test Z3950 comment annotations are created."""
        n_answers = 1
        target = 'example.com'
        anno_collection = 'http://eg.com/collection'
        task = self.ctx.create_task(n_answers,
                                    target,
                                    anno_collection=anno_collection)
        user = UserFactory()
        value = 'foo'
        TaskRunFactory.create_batch(n_answers,
                                    user=user,
                                    task=task,
                                    info={
                                        'control_number': '',
                                        'reference': '',
                                        'comments': value
                                    })
        result = self.result_repo.filter_by(project_id=task.project_id)[0]
        fake_search = MagicMock()
        fake_search.return_value = []
        mock_client.search_annotations = fake_search
        self.z3950_analyst.analyse(result.id)
        func = mock_client.create_annotation
        func.assert_called_once_with(
            anno_collection, {
                'motivation':
                'commenting',
                'type':
                'Annotation',
                'creator': {
                    'id': url_for('api.api_user', oid=user.id),
                    'type': 'Person',
                    'name': user.fullname,
                    'nickname': user.name
                },
                'generator':
                [{
                    "id": flask_app.config.get('GITHUB_REPO'),
                    "type": "Software",
                    "name": "LibCrowds",
                    "homepage": flask_app.config.get('SPA_SERVER_NAME')
                }, {
                    "id": url_for('api.api_task', oid=task.id),
                    "type": "Software"
                }],
                'body': {
                    'type': 'TextualBody',
                    'purpose': 'commenting',
                    'value': value,
                    'format': 'text/plain'
                },
                'target':
                target
            })

    @with_context
    @patch('pybossa_lc.model.base.wa_client')
    def test_transcriptions_are_normalised(self, mock_client):
        """Test Z3950 transcriptions are normalised according to set rules."""
        n_answers = 1
        target = 'example.com'
        anno_collection = 'http://eg.com/collection'
        rules = dict(case='title',
                     whitespace='full_stop',
                     trim_punctuation=True)
        task = self.ctx.create_task(n_answers,
                                    target,
                                    rules=rules,
                                    anno_collection=anno_collection)
        control_number = 'foo'
        references = ['OR 123  456.', 'Or.123.456. ', 'or 123 456']
        for value in references:
            TaskRunFactory.create(task=task,
                                  info={
                                      'reference': value,
                                      'control_number': control_number,
                                      'comments': ''
                                  })
        result = self.result_repo.filter_by(project_id=task.project_id)[0]
        fake_search = MagicMock()
        fake_search.return_value = []
        mock_client.search_annotations = fake_search
        self.z3950_analyst.analyse(result.id)
        assert_equal(
            mock_client.create_annotation.call_args_list, [
                call(
                    anno_collection, {
                        'motivation':
                        'describing',
                        'type':
                        'Annotation',
                        'generator':
                        [{
                            "id": flask_app.config.get('GITHUB_REPO'),
                            "type": "Software",
                            "name": "LibCrowds",
                            "homepage": flask_app.config.get('SPA_SERVER_NAME')
                        }, {
                            "id": url_for('api.api_task', oid=task.id),
                            "type": "Software"
                        }],
                        'body': [{
                            'type': 'TextualBody',
                            'purpose': 'describing',
                            'value': control_number.capitalize(),
                            'format': 'text/plain'
                        }, {
                            'type': 'TextualBody',
                            'purpose': 'tagging',
                            'value': 'control_number'
                        }],
                        'target':
                        target
                    }),
                call(
                    anno_collection, {
                        'motivation':
                        'describing',
                        'type':
                        'Annotation',
                        'generator': [
                            {
                                "id": flask_app.config.get('GITHUB_REPO'),
                                "type": "Software",
                                "name": "LibCrowds",
                                "homepage":
                                flask_app.config.get('SPA_SERVER_NAME')
                            }, {
                                "id": url_for('api.api_task', oid=task.id),
                                "type": "Software"
                            }
                        ],
                        'body': [{
                            'type': 'TextualBody',
                            'purpose': 'describing',
                            'value': 'Or.123.456',
                            'format': 'text/plain'
                        }, {
                            'type': 'TextualBody',
                            'purpose': 'tagging',
                            'value': 'reference'
                        }],
                        'target':
                        target
                    })
            ])

    @with_context
    @patch('pybossa_lc.model.base.wa_client')
    def test_with_matching_transcriptions(self, mock_client):
        """Test Z3950 results with matching transcriptions."""
        n_answers = 3
        target = 'example.com'
        anno_collection = 'http://eg.com/collection'
        task = self.ctx.create_task(n_answers,
                                    target,
                                    rules={},
                                    anno_collection=anno_collection)
        reference = 'foo'
        control_number = 'bar'
        TaskRunFactory.create_batch(n_answers,
                                    task=task,
                                    info={
                                        'reference': reference,
                                        'control_number': control_number,
                                        'comments': ''
                                    })
        result = self.result_repo.filter_by(project_id=task.project_id)[0]
        fake_search = MagicMock()
        fake_search.return_value = []
        mock_client.search_annotations = fake_search
        self.z3950_analyst.analyse(result.id)
        func = mock_client.create_annotation
        assert_equal(
            mock_client.create_annotation.call_args_list, [
                call(
                    anno_collection, {
                        'motivation':
                        'describing',
                        'type':
                        'Annotation',
                        'generator':
                        [{
                            "id": flask_app.config.get('GITHUB_REPO'),
                            "type": "Software",
                            "name": "LibCrowds",
                            "homepage": flask_app.config.get('SPA_SERVER_NAME')
                        }, {
                            "id": url_for('api.api_task', oid=task.id),
                            "type": "Software"
                        }],
                        'body': [{
                            'type': 'TextualBody',
                            'purpose': 'describing',
                            'value': control_number,
                            'format': 'text/plain'
                        }, {
                            'type': 'TextualBody',
                            'purpose': 'tagging',
                            'value': 'control_number'
                        }],
                        'target':
                        target
                    }),
                call(
                    anno_collection, {
                        'motivation':
                        'describing',
                        'type':
                        'Annotation',
                        'generator': [
                            {
                                "id": flask_app.config.get('GITHUB_REPO'),
                                "type": "Software",
                                "name": "LibCrowds",
                                "homepage":
                                flask_app.config.get('SPA_SERVER_NAME')
                            }, {
                                "id": url_for('api.api_task', oid=task.id),
                                "type": "Software"
                            }
                        ],
                        'body': [{
                            'type': 'TextualBody',
                            'purpose': 'describing',
                            'value': reference,
                            'format': 'text/plain'
                        }, {
                            'type': 'TextualBody',
                            'purpose': 'tagging',
                            'value': 'reference'
                        }],
                        'target':
                        target
                    })
            ])

    @with_context
    @patch('pybossa_lc.model.base.wa_client')
    def test_redundancy_increased_when_not_max(self, mock_client):
        """Test Z3950 task redundancy is updated when max not reached."""
        n_answers = 3
        target = 'example.com'
        task = self.ctx.create_task(n_answers, target, max_answers=4)
        for i in range(n_answers):
            TaskRunFactory.create(task=task,
                                  info={
                                      'reference': i,
                                      'control_number': i,
                                      'comments': ''
                                  })
        result = self.result_repo.filter_by(project_id=task.project_id)[0]
        fake_search = MagicMock()
        fake_search.return_value = []
        mock_client.search_annotations = fake_search
        self.z3950_analyst.analyse(result.id)
        assert_equal(mock_client.create_annotation.called, False)

        updated_task = self.task_repo.get_task(task.id)
        assert_equal(updated_task.n_answers, n_answers + 1)

    @with_context
    @patch('pybossa_lc.model.base.wa_client')
    def test_redundancy_not_increased_when_max(self, mock_client):
        """Test Z3950 task redundancy is not updated when max is reached."""
        n_answers = 3
        target = 'example.com'
        task = self.ctx.create_task(n_answers, target, max_answers=3)
        for i in range(n_answers):
            TaskRunFactory.create(task=task,
                                  info={
                                      'reference': i,
                                      'control_number': i,
                                      'comments': ''
                                  })
        result = self.result_repo.filter_by(project_id=task.project_id)[0]
        fake_search = MagicMock()
        fake_search.return_value = []
        mock_client.search_annotations = fake_search
        self.z3950_analyst.analyse(result.id)
        assert_equal(mock_client.create_annotation.called, False)

        updated_task = self.task_repo.get_task(task.id)
        assert_equal(updated_task.n_answers, n_answers)

    @with_context
    @patch('pybossa_lc.model.base.wa_client')
    def test_redundancy_not_increased_for_comments(self, mock_client):
        """Test Z3950 task redundancy is not updated for comments."""
        n_answers = 3
        target = 'example.com'
        task = self.ctx.create_task(n_answers,
                                    target,
                                    max_answers=n_answers + 1)
        for i in range(n_answers):
            TaskRunFactory.create(task=task,
                                  info={
                                      'reference': 'foo',
                                      'control_number': 'bar',
                                      'comments': i
                                  })
        result = self.result_repo.filter_by(project_id=task.project_id)[0]
        fake_search = MagicMock()
        fake_search.return_value = []
        mock_client.search_annotations = fake_search
        self.z3950_analyst.analyse(result.id)
        updated_task = self.task_repo.get_task(task.id)
        assert_equal(updated_task.n_answers, n_answers)

    @with_context
    @patch('pybossa_lc.model.base.wa_client')
    def test_redundancy_not_increased_when_no_values(self, mock_client):
        """Test Z3950 task redundancy is not updated when no values."""
        n_answers = 3
        target = 'example.com'
        task = self.ctx.create_task(n_answers,
                                    target,
                                    max_answers=n_answers + 1)
        for i in range(n_answers):
            TaskRunFactory.create(task=task,
                                  info={
                                      'reference': '',
                                      'control_number': '',
                                      'comments': ''
                                  })
        result = self.result_repo.filter_by(project_id=task.project_id)[0]
        fake_search = MagicMock()
        fake_search.return_value = []
        mock_client.search_annotations = fake_search
        self.z3950_analyst.analyse(result.id)
        updated_task = self.task_repo.get_task(task.id)
        assert_equal(updated_task.n_answers, n_answers)
        assert_equal(mock_client.create_annotation.called, False)

    @with_context
    @patch('pybossa_lc.model.base.wa_client')
    def test_old_annotations_deleted(self, mock_client):
        """Test Z3950 old Annotations deleted."""
        n_answers = 3
        target = 'example.com'
        task = self.ctx.create_task(n_answers, target)
        TaskRunFactory.create_batch(n_answers,
                                    task=task,
                                    info={
                                        'reference': '',
                                        'control_number': '',
                                        'comments': ''
                                    })
        result = self.result_repo.filter_by(project_id=task.project_id)[0]
        fake_annos = [{'id': 'baz'}, {'id': 'qux'}]
        fake_search = MagicMock()
        fake_search.return_value = fake_annos
        mock_client.search_annotations = fake_search
        self.z3950_analyst.analyse(result.id, analyse_full=True)
        mock_client.delete_batch.assert_called_once_with(fake_annos)

    @with_context
    @patch('pybossa_lc.model.base.wa_client')
    def test_results_with_annotations_not_analysed(self, mock_client):
        """Test results with Annotations already not analysed by default."""
        n_answers = 3
        target = 'example.com'
        task = self.ctx.create_task(n_answers, target)
        TaskRunFactory.create_batch(n_answers,
                                    task=task,
                                    info={
                                        'reference': 'foo',
                                        'control_number': 'bar',
                                        'comments': ''
                                    })
        result = self.result_repo.filter_by(project_id=task.project_id)[0]
        fake_annos = [{'id': 'baz'}, {'id': 'qux'}]
        fake_search = MagicMock()
        fake_search.return_value = fake_annos
        mock_client.search_annotations = fake_search
        self.z3950_analyst.analyse(result.id)
        assert_equal(mock_client.delete_batch.called, False)

    @with_context
    @patch('pybossa_lc.model.base.wa_client')
    def test_annotation_collection_iri_added_to_result_info(self, mock_client):
        """Test Z3950 result info updated with AnnotationCollection IRI."""
        n_answers = 3
        target = 'example.com'
        anno_collection = 'annotations.example.com'
        task = self.ctx.create_task(n_answers,
                                    target,
                                    anno_collection=anno_collection)
        TaskRunFactory.create_batch(n_answers,
                                    task=task,
                                    info={
                                        'reference': 'foo',
                                        'control_number': 'bar',
                                        'comments': ''
                                    })
        result = self.result_repo.filter_by(project_id=task.project_id)[0]
        fake_search = MagicMock()
        fake_search.return_value = []
        mock_client.search_annotations = fake_search
        self.z3950_analyst.analyse(result.id)
        assert_dict_equal(result.info, {'annotations': anno_collection})

    @with_context
    @patch('pybossa_lc.model.base.wa_client')
    def test_task_rejected(self, mock_client):
        """Test Z3950 annotation not created when task rejected * n_answers."""
        n_answers = 3
        target = 'example.com'
        anno_collection = 'annotations.example.com'
        task = self.ctx.create_task(n_answers,
                                    target,
                                    anno_collection=anno_collection)
        user = UserFactory()
        reason = 'invalid-task'
        TaskRunFactory.create_batch(n_answers,
                                    user=user,
                                    task=task,
                                    info={'reject': reason})
        result = self.result_repo.filter_by(project_id=task.project_id)[0]
        fake_search = MagicMock()
        fake_search.return_value = []
        mock_client.search_annotations = fake_search
        self.z3950_analyst.analyse(result.id)
        assert not mock_client.create_annotation.called
        assert_dict_equal(result.info, {
            'annotations': anno_collection,
            'rejected': reason
        })
コード例 #14
0
class TestResultRepository(Test):

    def setUp(self):
        super(TestResultRepository, self).setUp()
        self.result_repo = ResultRepository(db)

    @with_context
    def create_result(self, n_answers=1, filter_by=False):
        task = TaskFactory.create(n_answers=n_answers)
        TaskRunFactory.create(task=task)
        if filter_by:
            return self.result_repo.filter_by(project_id=1)
        else:
            return self.result_repo.get_by(project_id=1)


    @with_context
    def test_get_return_none_if_no_result(self):
        """Test get method returns None if there is no result with the
        specified id"""

        result = self.result_repo.get(2)

        assert result is None, result


    @with_context
    def test_get_returns_result(self):
        """Test get method returns a result if exists"""

        n_answers = 1

        task = TaskFactory.create(n_answers=n_answers)
        task_run = TaskRunFactory.create(task=task)

        result = self.result_repo.filter_by(project_id=1)


        err_msg = "There should be a result"
        assert len(result) == 1, err_msg
        result = result[0]
        assert result.project_id == 1, err_msg
        assert result.task_id == task.id, err_msg
        assert len(result.task_run_ids) == n_answers, err_msg
        err_msg = "The task_run id is missing in the results array"
        for tr_id in result.task_run_ids:
            assert tr_id == task_run.id, err_msg

    @with_context
    def test_get_by_returns_result(self):
        """Test get_by method returns a result if exists"""

        n_answers = 1

        task = TaskFactory.create(n_answers=n_answers)
        task_run = TaskRunFactory.create(task=task)

        result = self.result_repo.get_by(project_id=1)


        err_msg = "There should be a result"
        assert result.project_id == 1, err_msg
        assert result.task_id == task.id, err_msg
        assert len(result.task_run_ids) == n_answers, err_msg
        err_msg = "The task_run id is missing in the results array"
        for tr_id in result.task_run_ids:
            assert tr_id == task_run.id, err_msg


    @with_context
    def test_get_returns_result_after_increasig_redundancy(self):
        """Test get method returns a result if after increasing redundancy"""

        n_answers = 1

        task = TaskFactory.create(n_answers=n_answers)
        task_run = TaskRunFactory.create(task=task)

        result = self.result_repo.filter_by(project_id=1)

        err_msg = "There should be a result"
        assert len(result) == 1, err_msg
        result = result[0]
        assert result.project_id == 1, err_msg
        assert result.task_id == task.id, err_msg
        assert len(result.task_run_ids) == n_answers, err_msg
        err_msg = "The task_run id is missing in the results array"
        for tr_id in result.task_run_ids:
            assert tr_id == task_run.id, err_msg

        # Increase redundancy
        tmp = task_repo.get_task(task.id)
        tmp.n_answers = 2
        task_repo.update(task)

        err_msg = "There should be only one result"
        results = result_repo.filter_by(project_id=1)
        assert len(results) == 1, err_msg
        task_run_2 = TaskRunFactory.create(task=task)

        err_msg = "There should be 1 results"
        results = result_repo.filter_by(project_id=1)
        assert len(results) == 1, err_msg

        err_msg = "There should be 2 results"
        results = result_repo.filter_by(project_id=1, last_version=False)
        assert len(results) == 2, err_msg

        assert results[1].project_id == 1, err_msg
        assert results[1].task_id == task.id, err_msg
        err_msg = "First result should have only one task run ID"
        assert len(results[0].task_run_ids) == 1, err_msg
        err_msg = "Second result should have only two task run IDs"
        assert len(results[1].task_run_ids) == 2, err_msg
        err_msg = "The task_run id is missing in the results array"
        for tr_id in results[1].task_run_ids:
            assert tr_id in [task_run.id, task_run_2.id], err_msg


    @with_context
    def test_get_returns_no_result(self):
        """Test get method does not return a result if task not completed"""

        n_answers = 3

        task = TaskFactory.create(n_answers=n_answers)
        TaskRunFactory.create(task=task)

        result = self.result_repo.filter_by(project_id=1)

        err_msg = "There should not be a result"
        assert len(result) == 0, err_msg

    @with_context
    def test_fulltext_search_result(self):
        """Test fulltext search in JSON info works."""
        result = self.create_result()
        text = 'something word you me bar'
        data = {'foo': text}
        result.info = data
        self.result_repo.update(result)

        info = 'foo::word'
        res = self.result_repo.filter_by(info=info, fulltextsearch='1')
        assert len(res) == 1, len(res)
        assert res[0][0].info['foo'] == text, res[0]

        res = self.result_repo.filter_by(info=info)
        assert len(res) == 0, len(res)

    @with_context
    def test_fulltext_search_result_01(self):
        """Test fulltext search in JSON info works."""
        result = self.create_result()
        text = 'something word you me bar'
        data = {'foo': text, 'bar': 'foo'}
        result.info = data
        self.result_repo.update(result)

        info = 'foo::word&bar|bar::foo'
        res = self.result_repo.filter_by(info=info, fulltextsearch='1')
        assert len(res) == 1, len(res)
        assert res[0][0].info['foo'] == text, res[0]


    @with_context
    def test_info_json_search_result(self):
        """Test search in JSON info works."""
        result = self.create_result()
        text = 'bar'
        data = {'foo': text}
        result.info = data
        self.result_repo.update(result)

        info = 'foo::bar'
        res = self.result_repo.filter_by(info=info)
        assert len(res) == 1, len(res)
        assert res[0].info['foo'] == text, res[0]


    @with_context
    def test_update(self):
        """Test update persists the changes made to the result"""

        result = self.create_result()
        result.info = dict(new='value')

        self.result_repo.update(result)
        updated_result = self.result_repo.get(result.id)

        assert updated_result.info['new'] == 'value', updated_result


    @with_context
    def test_update_fails_if_integrity_error(self):
        """Test update raises a DBIntegrityError if the instance to be updated
        lacks a required value"""

        result = self.create_result()
        result.project_id = None

        assert_raises(DBIntegrityError, self.result_repo.update, result)


    @with_context
    def test_update_only_updates_results(self):
        """Test update raises a WrongObjectError when an object which is not
        a Result instance is updated"""

        bad_object = dict()

        assert_raises(WrongObjectError, self.result_repo.update, bad_object)

    @with_context
    def test_delete_results_from_project(self):
        """Test delte_results_from_project works."""
        project = ProjectFactory.create()
        task = TaskFactory.create(project=project,n_answers=1)
        taskrun = TaskRunFactory.create(task=task, project=project)
        result = result_repo.get_by(project_id=task.project.id)
        assert result
        result_repo.delete_results_from_project(project)
        result = result_repo.get_by(project_id=task.project.id)
        assert result is None
コード例 #15
0
class TestResultAPI(TestAPI):
    def setUp(self):
        super(TestResultAPI, self).setUp()
        self.result_repo = ResultRepository(db)

    def create_result(self,
                      n_results=1,
                      n_answers=1,
                      owner=None,
                      filter_by=False):
        if owner:
            owner = owner
        else:
            owner = UserFactory.create()
        project = ProjectFactory.create(owner=owner)
        tasks = []
        for i in range(n_results):
            tasks.append(
                TaskFactory.create(n_answers=n_answers, project=project))
        for i in range(n_answers):
            for task in tasks:
                TaskRunFactory.create(task=task, project=project)
        if filter_by:
            return self.result_repo.filter_by(project_id=1)
        else:
            return self.result_repo.get_by(project_id=1)

    @with_context
    @patch('pybossa.api.task.TaskAPI._verify_auth')
    def test_result_query_without_params(self, auth):
        """ Test API Result query"""
        user = UserFactory.create()
        auth.return_value = True
        result = self.create_result(n_answers=10)
        res = self.app.get('/api/result?all=1&api_key=' + user.api_key)
        results = json.loads(res.data)
        assert len(results) == 1, results
        result = results[0]
        assert result['info'] is None, result
        assert len(result['task_run_ids']) == 10, result
        assert result['task_run_ids'] == [1, 2, 3, 4, 5, 6, 7, 8, 9,
                                          10], result
        assert result['project_id'] == 1, result
        assert result['task_id'] == 1, result
        assert result['created'] is not None, result

        # Related
        res = self.app.get('/api/result?related=True&all=1&api_key=' +
                           user.api_key)
        results = json.loads(res.data)
        assert len(results) == 1, results
        result = results[0]
        assert result['info'] is None, result
        assert len(result['task_run_ids']) == 10, result
        assert result['task_run_ids'] == [1, 2, 3, 4, 5, 6, 7, 8, 9,
                                          10], result
        assert result['project_id'] == 1, result
        assert result['task_id'] == 1, result
        assert result['created'] is not None, result
        assert result['task']['id'] == result['task_id'], result
        assert len(result['task_runs']) == 10, result
        for tr in result['task_runs']:
            assert tr['task_id'] == result['task_id'], tr
            url = '/api/taskrun?id=%s&related=True&all=1&api_key=%s' % (
                tr['id'], user.api_key)
            taskrun = self.app.get(url)
            taskrun = json.loads(taskrun.data)[0]
            assert taskrun['result']['id'] == result['id'], taskrun['result']
            assert taskrun['task']['id'] == result['task_id'], taskrun['task']
        url = '/api/task?&all=1&id=%s&related=True&api_key=%s' % (
            result['task_id'], user.api_key)
        task = self.app.get(url)
        task = json.loads(task.data)[0]
        assert task['result']['id'] == result['id'], task['result']
        for tr in task['task_runs']:
            assert tr['id'] in result['task_run_ids'], task['task']

        result = self.create_result(n_answers=10)
        result = result_repo.get(2)
        result.created = '2119-01-01T14:37:30.642119'
        result_repo.update(result)

        url = '/api/result?orderby=created&desc=true&all=1&api_key=' + user.api_key
        res = self.app.get(url)
        data = json.loads(res.data)
        print data
        err_msg = "It should get the last item first."
        assert data[0]['created'] == '2119-01-01T14:37:30.642119', err_msg

        url = '/api/result?orderby=id&desc=false&all=1&api_key=' + user.api_key
        res = self.app.get(url)
        data = json.loads(res.data)
        err_msg = "It should be sorted by id."
        assert data[1]['id'] == result.id, err_msg

        url = '/api/result?orderby=wrongattribute&api_key=' + user.api_key
        res = self.app.get(url)
        data = json.loads(res.data)
        err_msg = "It should be 415."
        assert data['status'] == 'failed', data
        assert data['status_code'] == 415, data
        assert 'has no attribute' in data['exception_msg'], data

        url = '/api/result?&all=1&api_key=' + user.api_key
        res = self.app.get(url)
        data = json.loads(res.data)
        err_msg = "It should get not the last item first."
        assert data[0]['created'] != '2119-01-01T14:37:30.642119', err_msg

        # The output should have a mime-type: application/json
        assert res.mimetype == 'application/json', res

    @with_context
    def test_result_query_without_params_with_context(self):
        """ Test API Result query with context."""
        user = UserFactory.create()
        result = self.create_result(n_answers=10)
        res = self.app.get('/api/result?&all=1&api_key=' + user.api_key)
        results = json.loads(res.data)
        assert len(results) == 1, results
        result = results[0]
        assert result['info'] is None, result
        assert len(result['task_run_ids']) == 10, result
        assert result['task_run_ids'] == [1, 2, 3, 4, 5, 6, 7, 8, 9,
                                          10], result
        assert result['project_id'] == 1, result
        assert result['task_id'] == 1, result
        assert result['created'] is not None, result

        # The output should have a mime-type: application/json
        assert res.mimetype == 'application/json', res

    @with_context
    def test_result_query_with_params(self):
        """Test API query for result with params works"""
        owner = UserFactory.create()
        results = self.create_result(n_results=10, filter_by=True, owner=owner)
        # Test for real field
        res = self.app.get("/api/result?api_key=" + owner.api_key)
        data = json.loads(res.data)
        # Should return one result
        assert len(data) == 10, data
        # Correct result
        assert data[0]['project_id'] == 1, data
        res = self.app.get("/api/project?api_key=" + owner.api_key)
        project = json.loads(res.data)
        assert len(project) == 1, project
        assert project[0]['owner_id'] == owner.id, project

        owner_two = UserFactory.create()
        res = self.app.get("/api/result?api_key=" + owner_two.api_key)
        data = json.loads(res.data)
        err_403 = "Regular user should be Forbidden from accessing results"
        assert res.status_code == 403, err_403

        owner_two = UserFactory.create()
        res = self.app.get("/api/result?all=1&api_key=" + owner_two.api_key)
        data = json.loads(res.data)
        assert res.status_code == 403, err_403

        # Valid field but wrong value
        url = "/api/result?project_id=99999999&api_key=" + owner.api_key
        res = self.app.get(url)
        data = json.loads(res.data)
        assert len(data) == 0, data

        # Multiple fields
        url = '/api/result?project_id=1&task_id=1&api_key=' + owner.api_key
        res = self.app.get(url)
        data = json.loads(res.data)
        # One result
        assert len(data) == 1, data
        # Correct result
        assert data[0]['project_id'] == 1, data
        assert data[0]['task_id'] == 1, data

        # Multiple fields
        url = '/api/result?project_id=1&task_id=1&api_key=' + owner_two.api_key
        res = self.app.get(url)
        assert res.status_code == 403, err_403
        url = '/api/result?all=1&project_id=1&task_id=1&api_key=' + owner_two.api_key
        res = self.app.get(url)
        assert res.status_code == 403, err_403

        # Limits
        url = "/api/result?project_id=1&limit=5&api_key=" + owner.api_key
        res = self.app.get(url)
        data = json.loads(res.data)
        for item in data:
            assert item['project_id'] == 1, item
        assert len(data) == 5, len(data)

        # Limits
        url = "/api/result?project_id=1&limit=5&api_key=" + owner_two.api_key
        res = self.app.get(url)
        assert res.status_code == 403, err_403

        # Limits
        url = "/api/result?all=1&project_id=1&limit=5&api_key=" + owner_two.api_key
        res = self.app.get(url)
        assert res.status_code == 403, err_403

        # Keyset pagination
        url = "/api/result?project_id=1&limit=5&last_id=1&api_key=" + owner.api_key
        res = self.app.get(url)
        data = json.loads(res.data)
        for item in data:
            assert item['project_id'] == 1, item
        assert len(data) == 5, data
        assert data[0]['id'] == 2, data[0]

        # Keyset pagination
        url = "/api/result?project_id=1&limit=5&last_id=1&api_key=" + owner_two.api_key
        res = self.app.get(url)
        assert res.status_code == 403, err_403

        # Keyset pagination
        url = "/api/result?all=1&project_id=1&limit=5&last_id=1&api_key=" + owner.api_key
        res = self.app.get(url)
        data = json.loads(res.data)
        for item in data:
            assert item['project_id'] == 1, item
        assert len(data) == 5, data
        assert data[0]['id'] == 2, data[0]

    @with_context
    def test_result_post(self):
        """Test API Result creation"""
        admin = UserFactory.create()
        user = UserFactory.create()
        non_owner = UserFactory.create()
        project = ProjectFactory.create(owner=user)
        data = dict(info='final result')

        # anonymous user
        # no api-key
        res = self.app.post('/api/result', data=json.dumps(data))
        error_msg = 'Should not be allowed to create'
        assert_equal(res.status, '401 UNAUTHORIZED', error_msg)

        ### real user but not allowed as not owner!
        res = self.app.post('/api/result?api_key=' + non_owner.api_key,
                            data=json.dumps(data))

        error_msg = 'Should not be able to post tasks for projects of others'
        assert_equal(res.status, '403 FORBIDDEN', error_msg)

        # now a real user
        res = self.app.post('/api/result?api_key=' + user.api_key,
                            data=json.dumps(data))
        assert_equal(res.status, '403 FORBIDDEN', error_msg)

        # now the root user
        res = self.app.post('/api/result?api_key=' + admin.api_key,
                            data=json.dumps(data))
        assert_equal(res.status, '400 BAD REQUEST', error_msg)

        # POST with not JSON data
        url = '/api/result?api_key=%s' % user.api_key
        res = self.app.post(url, data=data)
        err = json.loads(res.data)
        assert res.status_code == 415, err
        assert err['status'] == 'failed', err
        assert err['target'] == 'result', err
        assert err['action'] == 'POST', err
        assert err['exception_cls'] == 'ValueError', err

        # POST with not allowed args
        res = self.app.post(url + '&foo=bar', data=json.dumps(data))
        err = json.loads(res.data)
        assert res.status_code == 415, err
        assert err['status'] == 'failed', err
        assert err['target'] == 'result', err
        assert err['action'] == 'POST', err
        assert err['exception_cls'] == 'AttributeError', err

        # POST with fake data
        data['wrongfield'] = 13
        res = self.app.post(url, data=json.dumps(data))
        err = json.loads(res.data)
        assert res.status_code == 415, err
        assert err['status'] == 'failed', err
        assert err['target'] == 'result', err
        assert err['action'] == 'POST', err
        assert err['exception_cls'] == 'TypeError', err

    @with_context
    def test_result_post_with_reserved_fields_returns_error(self):
        user = UserFactory.create()
        project = ProjectFactory.create(owner=user)
        data = {'created': 'today', 'id': 222, 'project_id': project.id}

        res = self.app.post('/api/result?api_key=' + user.api_key,
                            data=json.dumps(data))

        assert res.status_code == 400, res.status_code
        error = json.loads(res.data)
        assert error['exception_msg'] == "Reserved keys in payload", error

    @with_context
    def test_result_put_with_reserved_fields_returns_error(self):
        user = UserFactory.create()
        result = self.create_result(owner=user)
        print result
        url = '/api/result/%s?api_key=%s' % (result.id, user.api_key)
        data = {'created': 'today', 'project_id': 1, 'id': 222}

        res = self.app.put(url, data=json.dumps(data))

        assert res.status_code == 400, res.status_code
        error = json.loads(res.data)
        assert error['exception_msg'] == "Reserved keys in payload", error

    @with_context
    def test_result_update(self):
        """Test API result update"""
        admin = UserFactory.create()
        user = UserFactory.create()
        make_subadmin(user)
        non_owner = UserFactory.create()
        data = dict(info=dict(foo='bar'))
        datajson = json.dumps(data)
        result = self.create_result(owner=user)

        ## anonymous
        res = self.app.put('/api/result/%s' % result.id, data=datajson)
        assert_equal(res.status, '401 UNAUTHORIZED', res.status)
        ### real user but not allowed as not owner!
        url = '/api/result/%s?api_key=%s' % (result.id, non_owner.api_key)
        res = self.app.put(url, data=datajson)
        assert_equal(res.status, '403 FORBIDDEN', res.status)

        ### real user
        url = '/api/result/%s?api_key=%s' % (result.id, user.api_key)
        res = self.app.put(url, data=datajson)
        out = json.loads(res.data)
        assert_equal(res.status, '200 OK', res.data)
        assert_equal(result.info['foo'], data['info']['foo'])
        assert result.id == out['id'], out

        ### root
        data = dict(info=dict(foo='root'))
        datajson = json.dumps(data)
        res = self.app.put('/api/result/%s?api_key=%s' %
                           (result.id, admin.api_key),
                           data=datajson)
        assert_equal(res.status, '200 OK', res.status)
        assert_equal(result.info['foo'], data['info']['foo'])
        assert result.id == out['id'], out

        # PUT with not JSON data
        res = self.app.put(url, data=None)
        err = json.loads(res.data)
        assert res.status_code == 415, err
        assert err['status'] == 'failed', err
        assert err['target'] == 'result', err
        assert err['action'] == 'PUT', err
        assert err['exception_cls'] == 'ValueError', err

        # PUT with not allowed args
        res = self.app.put(url + "&foo=bar", data=json.dumps(data))
        err = json.loads(res.data)
        assert res.status_code == 415, err
        assert err['status'] == 'failed', err
        assert err['target'] == 'result', err
        assert err['action'] == 'PUT', err
        assert err['exception_cls'] == 'AttributeError', err

        # PUT with fake data
        data['wrongfield'] = 13
        res = self.app.put(url, data=json.dumps(data))
        err = json.loads(res.data)
        assert res.status_code == 415, err
        assert err['status'] == 'failed', err
        assert err['target'] == 'result', err
        assert err['action'] == 'PUT', err
        assert err['exception_cls'] == 'TypeError', err

    @with_context
    def test_result_delete(self):
        """Test API result delete"""
        admin = UserFactory.create()
        user = UserFactory.create()
        non_owner = UserFactory.create()
        result = self.create_result(owner=user)

        ## anonymous
        res = self.app.delete('/api/result/%s' % result.id)
        error_msg = 'Anonymous should not be allowed to update'
        assert_equal(res.status, '401 UNAUTHORIZED', error_msg)

        ### real user but not allowed as not owner!
        url = '/api/result/%s?api_key=%s' % (result.id, non_owner.api_key)
        res = self.app.delete(url)
        error_msg = 'Should not be able to update tasks of others'
        assert_equal(res.status, '403 FORBIDDEN', error_msg)

        #### real user
        # DELETE with not allowed args
        res = self.app.delete(url + "&foo=bar")
        err = json.loads(res.data)
        assert res.status_code == 415, err
        assert err['status'] == 'failed', err
        assert err['target'] == 'result', err
        assert err['action'] == 'DELETE', err
        assert err['exception_cls'] == 'AttributeError', err

        # DELETE returns 403
        url = '/api/result/%s?api_key=%s' % (result.id, user.api_key)
        res = self.app.delete(url)
        assert_equal(res.status, '403 FORBIDDEN', res.data)

        #### root user
        url = '/api/result/%s?api_key=%s' % (result.id, admin.api_key)
        res = self.app.delete(url)
        assert_equal(res.status, '403 FORBIDDEN', res.data)

    @with_context
    def test_get_last_version(self):
        """Test API result returns always latest version."""
        result = self.create_result()
        project = project_repo.get(result.project_id)
        task = task_repo.get_task(result.task_id)
        task.n_answers = 2
        TaskRunFactory.create(task=task, project=project)
        result = result_repo.get_by(project_id=project.id)

        assert result.last_version is True, result.last_version

        result_id = result.id

        results = result_repo.filter_by(project_id=project.id,
                                        last_version=False)
        assert len(results) == 2, len(results)

        for r in results:
            if r.id == result_id:
                assert r.last_version is True, r.last_version
            else:
                assert r.last_version is False, r.last_version

    @with_context
    def test_result_admin_post(self):
        """Test API Result creation"""
        admin = UserFactory.create()
        user = UserFactory.create()
        project = ProjectFactory.create(owner=user)
        data = dict(info='final result')

        # now the root user
        res = self.app.post('/api/result?api_key=' + admin.api_key,
                            data=json.dumps(data))
        assert res.status == '400 BAD REQUEST', res.status
        assert json.loads(res.data)['exception_msg'] == 'Invalid task id'

        task = TaskFactory.create(project=project, n_answers=1)
        data['task_id'] = task.id

        res = self.app.post('/api/result?api_key=' + admin.api_key,
                            data=json.dumps(data))
        assert res.status == '400 BAD REQUEST', res.status
        assert json.loads(res.data)['exception_msg'] == 'Invalid task'

        taskrun = TaskRunFactory.create(task=task)

        res = self.app.post('/api/result?api_key=' + admin.api_key,
                            data=json.dumps(data))
        assert res.status == '400 BAD REQUEST', res.status
        assert json.loads(
            res.data)['exception_msg'] == 'Record is already present'

        from pybossa.core import result_repo
        result_repo.delete_results_from_project(project)

        res = self.app.post('/api/result?api_key=' + admin.api_key,
                            data=json.dumps(data))
        assert res.status == '200 OK', res.status
        res_data = json.loads(res.data)

        assert res_data['task_id'] == task.id
        assert res_data['project_id'] == project.id
        assert res_data['info'] == data['info']
        assert res_data['last_version']
        assert res_data['task_run_ids'] == [taskrun.id]
コード例 #16
0
 def setUp(self):
     super(TestBulkTaskIIIFEnhancedImport, self).setUp()
     self.result_repo = ResultRepository(db)
     self.manifest_uri = 'http://example.org/iiif/book1/manifest'
     self.canvas_id_base = 'http://example.org/iiif/book1/canvas/p{0}'
     self.img_id_base = 'http://example.org/images/book1-page{0}-img{1}'
コード例 #17
0
class TestBulkTaskIIIFEnhancedImport(Test):
    def setUp(self):
        super(TestBulkTaskIIIFEnhancedImport, self).setUp()
        self.result_repo = ResultRepository(db)
        self.manifest_uri = 'http://example.org/iiif/book1/manifest'
        self.canvas_id_base = 'http://example.org/iiif/book1/canvas/p{0}'
        self.img_id_base = 'http://example.org/images/book1-page{0}-img{1}'

    def create_manifest(self, canvases=1, images=1):
        manifest = {
            '@context': 'http://iiif.io/api/presentation/2/context.json',
            '@id': self.manifest_uri,
            '@type': 'sc:Manifest',
            'label': 'Foo',
            'sequences': [{
                '@type': 'sc:Sequence',
                'canvases': []
            }]
        }
        for i in range(canvases):
            canvas = {
                '@id': self.canvas_id_base.format(i),
                '@type': 'sc:Canvas',
                'label': 'Bar',
                'height': 100,
                'width': 100,
                'images': []
            }
            for j in range(images):
                image = {
                    '@type': 'oa:Annotation',
                    'motivation': 'sc:painting',
                    'resource': {
                        '@id': 'http://example.org/image{}.jpg'.format(j),
                        '@type': 'dctypes:Image',
                        'service': {
                            '@id': self.img_id_base.format(i, j)
                        }
                    },
                    'on': 'http://example.org/{}'.format(i)
                }
                canvas['images'].append(image)
            manifest['sequences'][0]['canvases'].append(canvas)
        return manifest

    @with_context
    def test_bl_tasks_created_with_bl_link(self, requests):
        """Test that non-BL tasks are created with a non-BL link."""
        manifest = self.create_manifest()
        headers = {'Content-Type': 'application/json'}
        response = FakeResponse(text=json.dumps(manifest),
                                status_code=200,
                                headers=headers,
                                encoding='utf-8')
        requests.get.return_value = response

        importer = BulkTaskIIIFEnhancedImporter(manifest_uri=self.manifest_uri)
        tasks = importer.tasks()
        assert_equal(len(tasks), 1)

        link_query = '?manifest={}#?cv=0'.format(self.manifest_uri)
        link = 'http://universalviewer.io/uv.html' + link_query
        assert_equal(tasks[0]['info']['link'], link)

    @with_context
    def test_non_bl_tasks_created_with_non_bl_link(self, requests):
        """Test that non-BL tasks are created with a non-BL link."""
        manifest = self.create_manifest()
        bl_manifest_id = 'https://api.bl.uk/metadata/iiif/id/manifest.json'
        manifest['@id'] = bl_manifest_id
        headers = {'Content-Type': 'application/json'}
        response = FakeResponse(text=json.dumps(manifest),
                                status_code=200,
                                headers=headers,
                                encoding='utf-8')
        requests.get.return_value = response

        importer = BulkTaskIIIFEnhancedImporter(manifest_uri=bl_manifest_id)
        tasks = importer.tasks()
        assert_equal(len(tasks), 1)

        link = 'http://access.bl.uk/item/viewer/id#?cv=0'
        assert_equal(tasks[0]['info']['link'], link)

    @with_context
    def test_exeption_if_no_collection_iri_for_parent(self, requests):
        """Test exception if no collection iri when child tasks generated."""
        manifest = self.create_manifest()
        headers = {'Content-Type': 'application/json'}
        response = FakeResponse(text=json.dumps(manifest),
                                status_code=200,
                                headers=headers,
                                encoding='utf-8')
        requests.get.return_value = response
        parent = ProjectFactory()
        task = TaskFactory(project=parent, n_answers=1)
        TaskRunFactory.create(task=task)
        importer = BulkTaskIIIFEnhancedImporter(manifest_uri=self.manifest_uri,
                                                parent_id=parent.id)
        assert_raises(BulkImportException, importer.tasks)

    @with_context
    @patch('pybossa_lc.model.base.wa_client')
    def test_child_tasks_generated(self, mock_wa_client, requests):
        """Test that child tasks are generated."""
        n_canvases = 3
        n_images = 1
        manifest = self.create_manifest(canvases=n_canvases, images=n_images)
        headers = {'Content-Type': 'application/json'}
        response = FakeResponse(text=json.dumps(manifest),
                                status_code=200,
                                headers=headers,
                                encoding='utf-8')
        requests.get.return_value = response
        anno_fixtures = AnnotationFixtures()
        anno_collection_iri = 'example.org/annotations'
        category = CategoryFactory(
            info={'annotations': {
                'results': anno_collection_iri
            }})
        parent = ProjectFactory(category=category)
        tasks = TaskFactory.create_batch(n_canvases,
                                         project=parent,
                                         n_answers=1)

        # Create some annotations for each parent task
        expected = []
        return_values = []
        for i, task in enumerate(tasks):
            canvas_id = self.canvas_id_base.format(i)
            for j in range(n_images):
                TaskRunFactory.create(task=task)
                img_id = self.img_id_base.format(i, j)

                annotations = [
                    anno_fixtures.create(motivation='tagging',
                                         source=canvas_id),
                    anno_fixtures.create(motivation='describing',
                                         source=canvas_id),
                    anno_fixtures.create(motivation='commenting',
                                         source=canvas_id)
                ]

                result = self.result_repo.get_by(task_id=task.id)
                result.info = dict(annotations=anno_collection_iri)
                self.result_repo.update(result)
                return_values.append(annotations)

                # Store expected task data to check later
                link_query = '?manifest={}#?cv={}'.format(self.manifest_uri, i)
                link = 'http://universalviewer.io/uv.html' + link_query
                for anno in annotations[:2]:
                    expected.append({
                        'manifest':
                        self.manifest_uri,
                        'target':
                        anno['target'],
                        'link':
                        link,
                        'tileSource':
                        '{}/info.json'.format(img_id),
                        'url':
                        '{}/full/max/0/default.jpg'.format(img_id),
                        'url_m':
                        '{}/full/240,/0/default.jpg'.format(img_id),
                        'url_b':
                        '{}/full/1024,/0/default.jpg'.format(img_id),
                        'parent_task_id':
                        task.id
                    })

        mock_wa_client.search_annotations.side_effect = return_values
        importer = BulkTaskIIIFEnhancedImporter(manifest_uri=self.manifest_uri,
                                                parent_id=parent.id)
        tasks = importer.tasks()
        task_info = [task['info'] for task in tasks]
        expected = sorted(expected, key=lambda x: x['target'])
        assert_equal(task_info, expected)

    @with_context
    @patch('pybossa_lc.model.base.wa_client')
    def test_has_child_added_to_parent_results(self, mock_wa_client, requests):
        """Test that the has_children key is added to parent results."""
        manifest = self.create_manifest()
        headers = {'Content-Type': 'application/json'}
        response = FakeResponse(text=json.dumps(manifest),
                                status_code=200,
                                headers=headers,
                                encoding='utf-8')
        requests.get.return_value = response
        anno_collection_iri = 'example.org/annotations'

        # Create a task for each canvas
        n_tasks = 3
        category = CategoryFactory(
            info={'annotations': {
                'results': anno_collection_iri
            }})
        parent = ProjectFactory(category=category)
        tasks = TaskFactory.create_batch(n_tasks, project=parent, n_answers=1)
        for task in tasks:
            TaskRunFactory.create(task=task)
            result = self.result_repo.get_by(task_id=task.id)
            result.info = dict(annotations=anno_collection_iri)
            self.result_repo.update(result)

        importer = BulkTaskIIIFEnhancedImporter(manifest_uri=self.manifest_uri,
                                                parent_id=parent.id)
        mock_wa_client.search_annotations.return_value = []
        tasks = importer.tasks()

        results = self.result_repo.filter_by(project_id=parent.id)
        result_info = [result.info for result in results]
        expected = [{
            'annotations': anno_collection_iri,
            'has_children': True
        }] * n_tasks
        assert_equal(result_info, expected)
コード例 #18
0
    def setUp(self):
        super(TestIIIFAnnotationAnalyst, self).setUp()
        self.ctx = ContextFixtures()
        self.result_repo = ResultRepository(db)
        self.task_repo = TaskRepository(db)
        self.iiif_analyst = IIIFAnnotationAnalyst()
        self.comments = ['Some comment']
        self.tags = {
            'foo': [
                dict(x=100, y=100, w=100, h=100),
                dict(x=200, y=200, w=200, h=200)
            ],
            'bar': [dict(x=300, y=300, w=300, h=300)]
        }
        transcription_data = {'foo': ['bar', 'baz'], 'qux': ['quux', 'quuz']}
        self.transcriptions_df = pandas.DataFrame(transcription_data)

        self.comment_annos = []
        for comment in self.comments:
            self.comment_annos.append({
                'motivation': 'commenting',
                'body': {
                    'type': 'TextualBody',
                    'value': comment,
                    'purpose': 'commenting',
                    'format': 'text/plain'
                },
                'target': 'example.com'
            })

        self.tagging_annos = []
        for tag, rect_list in self.tags.items():
            for rect in rect_list:
                self.tagging_annos.append({
                    'motivation': 'tagging',
                    'body': {
                        'type': 'TextualBody',
                        'purpose': 'tagging',
                        'value': tag
                    },
                    'target': {
                        'source': 'example.com',
                        'selector': {
                            'conformsTo':
                            'http://www.w3.org/TR/media-frags/',
                            'type':
                            'FragmentSelector',
                            'value':
                            '?xywh={0},{1},{2},{3}'.format(
                                rect['x'], rect['y'], rect['w'], rect['h'])
                        }
                    }
                })

        self.transcription_annos = []
        for tag, value_list in transcription_data.items():
            for value in value_list:
                self.transcription_annos.append({
                    'motivation':
                    'describing',
                    'body': [{
                        'type': 'TextualBody',
                        'purpose': 'tagging',
                        'value': tag
                    }, {
                        'type': 'TextualBody',
                        'purpose': 'describing',
                        'value': value,
                        'format': 'text/plain'
                    }],
                    'target':
                    'example.com'
                })

        self.data = {
            'user_id': [1, 2, 3],
            'info':
            [self.comment_annos, self.tagging_annos, self.transcription_annos]
        }
コード例 #19
0
ファイル: test_result.py プロジェクト: vasysta/pybossa
class TestResultAPI(TestAPI):

    def setUp(self):
        super(TestResultAPI, self).setUp()
        self.result_repo = ResultRepository(db)


    def create_result(self, n_results=1, n_answers=1, owner=None,
                      filter_by=False):
        if owner:
            owner = owner
        else:
            owner = UserFactory.create()
        project = ProjectFactory.create(owner=owner)
        tasks = []
        for i in range(n_results):
            tasks.append(TaskFactory.create(n_answers=n_answers,
                                            project=project))
        for i in range(n_answers):
            for task in tasks:
                TaskRunFactory.create(task=task, project=project)
        if filter_by:
            return self.result_repo.filter_by(project_id=1)
        else:
            return self.result_repo.get_by(project_id=1)


    @with_context
    def test_result_query_without_params(self):
        """ Test API Result query"""
        result = self.create_result(n_answers=10)
        res = self.app.get('/api/result')
        results = json.loads(res.data)
        assert len(results) == 1, results
        result = results[0]
        assert result['info'] is None, result
        assert len(result['task_run_ids']) == 10, result
        assert result['task_run_ids'] == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], result
        assert result['project_id'] == 1, result
        assert result['task_id'] == 1, result
        assert result['created'] is not None, result

        # The output should have a mime-type: application/json
        assert res.mimetype == 'application/json', res


    @with_context
    def test_result_query_with_params(self):
        """Test API query for result with params works"""
        results = self.create_result(n_results=10, filter_by=True)
        # Test for real field
        res = self.app.get("/api/result?project_id=1")
        data = json.loads(res.data)
        # Should return one result
        assert len(data) == 10, data
        # Correct result
        assert data[0]['project_id'] == 1, data

        # Valid field but wrong value
        res = self.app.get("/api/result?project_id=99999999")
        data = json.loads(res.data)
        assert len(data) == 0, data

        # Multiple fields
        res = self.app.get('/api/result?project_id=1&task_id=1')
        data = json.loads(res.data)
        # One result
        assert len(data) == 1, data
        # Correct result
        assert data[0]['project_id'] == 1, data
        assert data[0]['task_id'] == 1, data

        # Limits
        res = self.app.get("/api/result?project_id=1&limit=5")
        data = json.loads(res.data)
        for item in data:
            assert item['project_id'] == 1, item
        assert len(data) == 5, len(data)

        # Keyset pagination
        url = "/api/result?project_id=1&limit=5&last_id=1"
        res = self.app.get(url)
        data = json.loads(res.data)
        for item in data:
            assert item['project_id'] == 1, item
        assert len(data) == 5, data
        assert data[0]['id'] == 2, data[0]


    @with_context
    def test_result_post(self):
        """Test API Result creation"""
        admin = UserFactory.create()
        user = UserFactory.create()
        non_owner = UserFactory.create()
        project = ProjectFactory.create(owner=user)
        data = dict(info='final result')

        # anonymous user
        # no api-key
        res = self.app.post('/api/result', data=json.dumps(data))
        error_msg = 'Should not be allowed to create'
        assert_equal(res.status, '401 UNAUTHORIZED', error_msg)

        ### real user but not allowed as not owner!
        res = self.app.post('/api/result?api_key=' + non_owner.api_key,
                            data=json.dumps(data))

        error_msg = 'Should not be able to post tasks for projects of others'
        assert_equal(res.status, '403 FORBIDDEN', error_msg)

        # now a real user
        res = self.app.post('/api/result?api_key=' + user.api_key,
                            data=json.dumps(data))
        assert_equal(res.status, '403 FORBIDDEN', error_msg)

        # now the root user
        res = self.app.post('/api/result?api_key=' + admin.api_key,
                            data=json.dumps(data))
        assert_equal(res.status, '403 FORBIDDEN', error_msg)

        # POST with not JSON data
        url = '/api/result?api_key=%s' % user.api_key
        res = self.app.post(url, data=data)
        err = json.loads(res.data)
        assert res.status_code == 415, err
        assert err['status'] == 'failed', err
        assert err['target'] == 'result', err
        assert err['action'] == 'POST', err
        assert err['exception_cls'] == 'ValueError', err

        # POST with not allowed args
        res = self.app.post(url + '&foo=bar', data=json.dumps(data))
        err = json.loads(res.data)
        assert res.status_code == 415, err
        assert err['status'] == 'failed', err
        assert err['target'] == 'result', err
        assert err['action'] == 'POST', err
        assert err['exception_cls'] == 'AttributeError', err

        # POST with fake data
        data['wrongfield'] = 13
        res = self.app.post(url, data=json.dumps(data))
        err = json.loads(res.data)
        assert res.status_code == 415, err
        assert err['status'] == 'failed', err
        assert err['target'] == 'result', err
        assert err['action'] == 'POST', err
        assert err['exception_cls'] == 'TypeError', err

    def test_result_post_with_reserved_fields_returns_error(self):
        user = UserFactory.create()
        project = ProjectFactory.create(owner=user)
        data = {'created': 'today',
                'id': 222, 'project_id': project.id}

        res = self.app.post('/api/result?api_key=' + user.api_key,
                            data=json.dumps(data))

        assert res.status_code == 400, res.status_code
        error = json.loads(res.data)
        assert error['exception_msg'] == "Reserved keys in payload", error

    def test_result_put_with_reserved_fields_returns_error(self):
        user = UserFactory.create()
        result = self.create_result(owner=user)
        print result
        url = '/api/result/%s?api_key=%s' % (result.id, user.api_key)
        data = {'created': 'today',
                'project_id': 1,
                'id': 222}

        res = self.app.put(url, data=json.dumps(data))

        assert res.status_code == 400, res.status_code
        error = json.loads(res.data)
        assert error['exception_msg'] == "Reserved keys in payload", error

    @with_context
    def test_result_update(self):
        """Test API result update"""
        admin = UserFactory.create()
        user = UserFactory.create()
        non_owner = UserFactory.create()
        data = dict(info=dict(foo='bar'))
        datajson = json.dumps(data)
        result = self.create_result(owner=user)

        ## anonymous
        res = self.app.put('/api/result/%s' % result.id, data=datajson)
        assert_equal(res.status, '401 UNAUTHORIZED', res.status)
        ### real user but not allowed as not owner!
        url = '/api/result/%s?api_key=%s' % (result.id, non_owner.api_key)
        res = self.app.put(url, data=datajson)
        assert_equal(res.status, '403 FORBIDDEN', res.status)

        ### real user
        url = '/api/result/%s?api_key=%s' % (result.id, user.api_key)
        res = self.app.put(url, data=datajson)
        out = json.loads(res.data)
        assert_equal(res.status, '200 OK', res.data)
        assert_equal(result.info['foo'], data['info']['foo'])
        assert result.id == out['id'], out

        ### root
        res = self.app.put('/api/result/%s?api_key=%s' % (result.id, admin.api_key),
                           data=datajson)
        assert_equal(res.status, '403 FORBIDDEN', res.status)

        # PUT with not JSON data
        res = self.app.put(url, data=None)
        err = json.loads(res.data)
        assert res.status_code == 415, err
        assert err['status'] == 'failed', err
        assert err['target'] == 'result', err
        assert err['action'] == 'PUT', err
        assert err['exception_cls'] == 'ValueError', err

        # PUT with not allowed args
        res = self.app.put(url + "&foo=bar", data=json.dumps(data))
        err = json.loads(res.data)
        assert res.status_code == 415, err
        assert err['status'] == 'failed', err
        assert err['target'] == 'result', err
        assert err['action'] == 'PUT', err
        assert err['exception_cls'] == 'AttributeError', err

        # PUT with fake data
        data['wrongfield'] = 13
        res = self.app.put(url, data=json.dumps(data))
        err = json.loads(res.data)
        assert res.status_code == 415, err
        assert err['status'] == 'failed', err
        assert err['target'] == 'result', err
        assert err['action'] == 'PUT', err
        assert err['exception_cls'] == 'TypeError', err


    @with_context
    def test_result_delete(self):
        """Test API result delete"""
        admin = UserFactory.create()
        user = UserFactory.create()
        non_owner = UserFactory.create()
        result = self.create_result(owner=user)

        ## anonymous
        res = self.app.delete('/api/result/%s' % result.id)
        error_msg = 'Anonymous should not be allowed to update'
        assert_equal(res.status, '401 UNAUTHORIZED', error_msg)

        ### real user but not allowed as not owner!
        url = '/api/result/%s?api_key=%s' % (result.id, non_owner.api_key)
        res = self.app.delete(url)
        error_msg = 'Should not be able to update tasks of others'
        assert_equal(res.status, '403 FORBIDDEN', error_msg)

        #### real user
        # DELETE with not allowed args
        res = self.app.delete(url + "&foo=bar")
        err = json.loads(res.data)
        assert res.status_code == 415, err
        assert err['status'] == 'failed', err
        assert err['target'] == 'result', err
        assert err['action'] == 'DELETE', err
        assert err['exception_cls'] == 'AttributeError', err

        # DELETE returns 403
        url = '/api/result/%s?api_key=%s' % (result.id, user.api_key)
        res = self.app.delete(url)
        assert_equal(res.status, '403 FORBIDDEN', res.data)

        #### root user
        url = '/api/result/%s?api_key=%s' % (result.id, admin.api_key)
        res = self.app.delete(url)
        assert_equal(res.status, '403 FORBIDDEN', res.data)

    @with_context
    def test_get_last_version(self):
        """Test API result returns always latest version."""
        result = self.create_result()
        project = project_repo.get(result.project_id)
        task = task_repo.get_task(result.task_id)
        task.n_answers = 2
        TaskRunFactory.create(task=task, project=project)
        result = result_repo.get_by(project_id=project.id)

        assert result.last_version is True, result.last_version

        result_id = result.id

        results = result_repo.filter_by(project_id=project.id, last_version=False)
        assert len(results) == 2, len(results)

        for r in results:
            if r.id == result_id:
                assert r.last_version is True, r.last_version
            else:
                assert r.last_version is False, r.last_version
コード例 #20
0
class TestBaseAnalyst(Test):
    def setUp(self):
        super(TestBaseAnalyst, self).setUp()
        BaseAnalyst.__abstractmethods__ = frozenset()
        self.ctx = ContextFixtures()
        self.base_analyst = BaseAnalyst()
        self.project_repo = ProjectRepository(db)
        self.result_repo = ResultRepository(db)
        self.task_repo = TaskRepository(db)
        assert_dict_equal.__self__.maxDiff = None
        assert_equal.__self__.maxDiff = None

    @with_context
    @patch("pybossa_lc.analysis.base.BaseAnalyst.analyse")
    def test_analyse_all(self, mock_analyse):
        """Test that all results are analysed."""
        project = ProjectFactory()
        tasks = TaskFactory.create_batch(2, project=project, n_answers=1)
        for task in tasks:
            TaskRunFactory.create(task=task)
        result = self.result_repo.get_by(task_id=tasks[0].id)
        result.info = dict(annotations=[{}])
        self.result_repo.update(result)
        self.base_analyst.analyse_all(project.id)
        expected = [call(t.id, analyse_full=True) for t in tasks]
        assert_equal(mock_analyse.call_args_list, expected)

    @with_context
    @patch("pybossa_lc.analysis.base.BaseAnalyst.analyse")
    def test_analyse_empty(self, mock_analyse):
        """Test that empty results are analysed."""
        project = ProjectFactory()
        tasks = TaskFactory.create_batch(2, project=project, n_answers=1)
        for task in tasks:
            TaskRunFactory.create(task=task)
        result = self.result_repo.get_by(task_id=tasks[0].id)
        result.info = dict(annotations=[{}])
        self.result_repo.update(result)
        all_results = self.result_repo.filter_by(project_id=project.id)
        self.base_analyst.analyse_empty(project.id)
        expected = [call(r.task_id) for r in all_results if not r.info]
        assert_equal(mock_analyse.call_args_list, expected)

    @with_context
    def test_key_dropped(self):
        """Test the correct keys are dropped."""
        data = [{'foo': None, 'bar': None}]
        df = pandas.DataFrame(data, range(len(data)))
        excluded = ['foo']
        df = self.base_analyst.drop_keys(df, excluded)
        assert_not_in('foo', df.keys())
        assert_in('bar', df.keys())

    @with_context
    def test_empty_rows_dropped(self):
        """Test empty rows are dropped."""
        data = [{'foo': 'bar'}, {'foo': None}]
        df = pandas.DataFrame(data, range(len(data)))
        df = self.base_analyst.drop_empty_rows(df)
        assert_equals(df['foo'].tolist(), ['bar'])

    @with_context
    def test_partial_rows_not_dropped(self):
        """Test partial rows are not dropped."""
        data = [{'foo': 'bar', 'baz': None}]
        df = pandas.DataFrame(data, range(len(data)))
        df = self.base_analyst.drop_empty_rows(df)
        expected = {'foo': {0: 'bar'}, 'baz': {0: None}}
        assert_dict_equal(df.to_dict(), expected)

    @with_context
    def test_match_fails_when_percentage_not_met(self):
        """Test False is returned when min answers not met."""
        data = [{'foo': 'bar', 'baz': None}]
        df = pandas.DataFrame(data, range(len(data)))
        min_answers = 2
        has_matches = self.base_analyst.has_n_matches(min_answers, df)
        assert_equal(has_matches, False)

    @with_context
    def test_match_fails_when_nan_cols(self):
        """Test False is returned when NaN columns only."""
        data = [{'foo': None}]
        df = pandas.DataFrame(data, range(len(data)))
        df = df.replace('', numpy.nan)
        min_answers = 2
        has_matches = self.base_analyst.has_n_matches(min_answers, df)
        assert_equal(has_matches, False)

    @with_context
    def test_match_succeeds_when_percentage_met(self):
        """Test True returned when match percentage met."""
        data = [{'foo': 'bar'}, {'foo': 'bar'}]
        df = pandas.DataFrame(data, range(len(data)))
        min_answers = 2
        has_matches = self.base_analyst.has_n_matches(min_answers, df)
        assert_equal(has_matches, True)

    @with_context
    def test_get_dataframe_with_dict(self):
        """Test the task run dataframe with a dict as the info."""
        info = {'foo': 'bar'}
        n_task_runs = 2
        task = TaskFactory()
        taskruns = TaskRunFactory.create_batch(n_task_runs,
                                               task=task,
                                               info=info)
        df = self.base_analyst.get_task_run_df(task, taskruns)
        assert_equal(df['foo'].tolist(), [info['foo']] * n_task_runs)
        assert_equal(df['info'].tolist(), [info] * n_task_runs)

    @with_context
    def test_get_dataframe_with_list(self):
        """Test the task run dataframe with a list as the info."""
        info = [{'foo': 'bar'}, {'baz': 'qux'}]
        n_task_runs = 2
        task = TaskFactory()
        taskruns = TaskRunFactory.create_batch(n_task_runs,
                                               task=task,
                                               info=info)
        df = self.base_analyst.get_task_run_df(task, taskruns)
        assert_equal(df['info'].tolist(), [info] * n_task_runs)

    @with_context
    def test_protected_keys_prefixed_when_exploded(self):
        """Test that protected info keys are prefixed."""
        info = {'foo': 'bar', 'info': 'baz'}
        task = TaskFactory()
        taskrun = TaskRunFactory.create(task=task, info=info)
        df = self.base_analyst.get_task_run_df(task, [taskrun])
        assert_equal(df['_info'].tolist(), [info['info']])

    @with_context
    def test_user_ids_in_task_run_dataframe(self):
        """Test that user IDs are included in the task run dataframe."""
        task = TaskFactory()
        taskruns = TaskRunFactory.create_batch(2, task=task)
        df = self.base_analyst.get_task_run_df(task, taskruns)
        assert_equal(df['user_id'].tolist(), [tr.user_id for tr in taskruns])

    def test_titlecase_normalisation(self):
        """Test titlecase normalisation."""
        rules = dict(case='title')
        norm = self.base_analyst.normalise_transcription('Some words', rules)
        assert_equal(norm, 'Some Words')

    def test_lowercase_normalisation(self):
        """Test lowercase normalisation."""
        rules = dict(case='lower')
        norm = self.base_analyst.normalise_transcription('Some words', rules)
        assert_equal(norm, 'some words')

    def test_uppercase_normalisation(self):
        """Test uppercase normalisation."""
        rules = dict(case='upper')
        norm = self.base_analyst.normalise_transcription('Some words', rules)
        assert_equal(norm, 'SOME WORDS')

    def test_whitespace_normalisation(self):
        """Test whitespace normalisation."""
        rules = dict(whitespace='normalise')
        norm = self.base_analyst.normalise_transcription(' Two  Words', rules)
        assert_equal(norm, 'Two Words')

    def test_whitespace_replace_underscore(self):
        """Test replacing whitespace with underscore normalisation."""
        rules = dict(whitespace='underscore')
        norm = self.base_analyst.normalise_transcription(' Two  Words', rules)
        assert_equal(norm, 'Two_Words')

    def test_whitespace_replace_full_stop(self):
        """Test replacing whitespace with full stop normalisation."""
        rules = dict(whitespace='full_stop')
        norm = self.base_analyst.normalise_transcription(' Two  Words', rules)
        assert_equal(norm, 'Two.Words')

    def test_trim_punctuation_normalisation(self):
        """Test trim punctuation normalisation."""
        rules = dict(trim_punctuation=True)
        norm = self.base_analyst.normalise_transcription(':Oh, a word.', rules)
        assert_equal(norm, 'Oh, a word')

    def test_date_not_normalised_if_rule_inactive(self):
        """Test date conversion not applied of rule not activate."""
        norm = self.base_analyst.normalise_transcription('foo', {})
        assert_equal(norm, 'foo')

    def test_date_conversion_with_slash(self):
        """Test date conversion with slash seperators."""
        rules = dict(date_format=True, dayfirst=True)
        norm = self.base_analyst.normalise_transcription('19/11/1984', rules)
        assert_equal(norm, '1984-11-19')

    def test_date_conversion_with_hyphen(self):
        """Test date conversion with hyphen seperator."""
        rules = dict(date_format=True, dayfirst=True)
        norm = self.base_analyst.normalise_transcription('19-11-1984', rules)
        assert_equal(norm, '1984-11-19')

    def test_date_conversion_with_no_seperator(self):
        """Test date conversion with no seperator."""
        rules = dict(date_format=True, dayfirst=True)
        norm = self.base_analyst.normalise_transcription('19111984', rules)
        assert_equal(norm, '')

    def test_date_conversion_with_no_year_and_year_last(self):
        """Test date conversion with no year and year last."""
        rules = dict(date_format=True, dayfirst=True)
        norm = self.base_analyst.normalise_transcription('19/11', rules)
        assert_equal(norm, '-11-19')

    def test_date_conversion_with_no_year_and_year_first(self):
        """Test date conversion with no year and year first."""
        rules = dict(date_format=True, yearfirst=True)
        norm = self.base_analyst.normalise_transcription('11/19', rules)
        assert_equal(norm, '-11-19')

    def test_date_conversion_with_invalid_string(self):
        """Test date conversion with invalid string."""
        rules = dict(date_format=True, dayfirst=True)
        norm = self.base_analyst.normalise_transcription('No date', rules)
        assert_equal(norm, '')

    def test_date_conversion_with_zero(self):
        """Test date conversion with zero."""
        rules = dict(date_format=True, dayfirst=True)
        norm = self.base_analyst.normalise_transcription('0', rules)
        assert_equal(norm, '')

    def test_date_conversion_with_non_zero_integer(self):
        """Test date conversion with non-zero integer."""
        rules = dict(date_format=True, dayfirst=True)
        norm = self.base_analyst.normalise_transcription('1', rules)
        assert_equal(norm, '')

    def test_date_conversion_with_trailing_punctuation(self):
        """Test date conversion with trailing punctuation."""
        rules = dict(date_format=True, dayfirst=True)
        norm = self.base_analyst.normalise_transcription('19/11/', rules)
        assert_equal(norm, '-11-19')

    def test_date_conversion_with_trailing_whitespace(self):
        """Test date conversion with trailing whitespace."""
        rules = dict(date_format=True, dayfirst=True)
        norm = self.base_analyst.normalise_transcription('19/11/1984 ', rules)
        assert_equal(norm, '1984-11-19')

    @with_context
    def test_n_answers_increased_when_task_complete(self):
        """Test n answers required for a task is updated."""
        n_original_answers = 1
        task = TaskFactory.create(n_answers=n_original_answers)
        TaskRunFactory.create(task=task)
        self.base_analyst.update_n_answers_required(task, False)
        assert_equal(task.n_answers, n_original_answers + 1)
        assert_equal(task.state, 'ongoing')

    @with_context
    def test_n_answers_not_increased_when_still_task_runs(self):
        """Test n answers not updated when task runs still required."""
        n_original_answers = 2
        task = TaskFactory.create(n_answers=n_original_answers)
        TaskRunFactory.create(task=task)
        self.base_analyst.update_n_answers_required(task, False)
        assert_equal(task.n_answers, n_original_answers)
        assert_equal(task.state, 'ongoing')

    @with_context
    def test_n_answers_not_increased_when_max_answers_reached(self):
        """Test n answers not updated when max answers reached."""
        n_answers = 3
        task = TaskFactory.create(n_answers=n_answers)
        TaskRunFactory.create_batch(n_answers, task=task)
        self.base_analyst.update_n_answers_required(task,
                                                    False,
                                                    max_answers=n_answers)
        assert_equal(task.n_answers, n_answers)
        assert_equal(task.state, 'completed')

    @with_context
    def test_n_answers_reduced_when_task_complete(self):
        """Test n answers reduced to number of task runs when task complete."""
        n_answers = 3
        task = TaskFactory.create(n_answers=n_answers)
        TaskRunFactory.create_batch(n_answers - 1, task=task)
        self.base_analyst.update_n_answers_required(task,
                                                    True,
                                                    max_answers=n_answers)
        assert_equal(task.n_answers, n_answers - 1)
        assert_equal(task.state, 'completed')

    def test_overlap_ratio_is_1_with_equal_rects(self):
        """Test for an overlap ratio of 1."""
        rect = {'x': 100, 'y': 100, 'w': 100, 'h': 100}
        overlap = self.base_analyst.get_overlap_ratio(rect, rect)
        assert_equal(overlap, 1)

    def test_overlap_ratio_is_0_with_adjacent_rects(self):
        """Test for an overlap ratio of 0."""
        r1 = {'x': 100, 'y': 100, 'w': 100, 'h': 100}
        r2 = {'x': 100, 'y': 201, 'w': 100, 'h': 100}
        overlap = self.base_analyst.get_overlap_ratio(r1, r2)
        assert_equal(overlap, 0)

    def test_overlap_ratio_with_partially_overlapping_rects(self):
        """Test for an overlap ratio of 0.33."""
        r1 = {'x': 100, 'y': 100, 'w': 100, 'h': 100}
        r2 = {'x': 150, 'y': 100, 'w': 100, 'h': 100}
        overlap = self.base_analyst.get_overlap_ratio(r1, r2)
        assert_equal('{:.2f}'.format(overlap), '0.33')

    def test_overlap_ratio_where_union_is_zero(self):
        """Test for an overlap ratio where the union is zero."""
        r1 = {'x': 0, 'y': 0, 'w': 100, 'h': 100}
        r2 = {'x': 101, 'y': 0, 'w': 100, 'h': 100}
        overlap = self.base_analyst.get_overlap_ratio(r1, r2)
        assert_equal(overlap, 0)

    def test_rect_from_selection(self):
        """Test that we get the correct rect."""
        coords = dict(x=400, y=200, w=100, h=150)
        coords_str = '{0},{1},{2},{3}'.format(coords['x'], coords['y'],
                                              coords['w'], coords['h'])
        fake_anno = {
            'target': {
                'selector': {
                    'value': '?xywh={}'.format(coords_str)
                }
            }
        }
        rect = self.base_analyst.get_rect_from_selection_anno(fake_anno)
        assert_dict_equal(rect, coords)

    def test_rect_from_selection_with_floats(self):
        """Test that we get the correct rect with rounded coordinates."""
        coords = dict(x=400.001, y=200.499, w=100.501, h=150.999)
        coords_str = '{0},{1},{2},{3}'.format(coords['x'], coords['y'],
                                              coords['w'], coords['h'])
        fake_anno = {
            'target': {
                'selector': {
                    'value': '?xywh={}'.format(coords_str)
                }
            }
        }
        rect = self.base_analyst.get_rect_from_selection_anno(fake_anno)
        assert_dict_equal(rect, {'x': 400, 'y': 200, 'w': 101, 'h': 151})

    @with_context
    def test_get_project_template(self):
        """Test that the correct template is returned."""
        category = CategoryFactory()
        tmpl_fixtures = TemplateFixtures(category)
        tmpl1 = tmpl_fixtures.create()
        tmpl2 = tmpl_fixtures.create()
        fake_templates = [tmpl1, tmpl2]
        category.info = dict(templates=fake_templates)
        self.project_repo.update_category(category)
        project_info = dict(template_id=tmpl1['id'])
        project = ProjectFactory(category=category, info=project_info)
        ret_tmpl = self.base_analyst.get_project_template(project)
        assert_equal(ret_tmpl, tmpl1)

    @with_context
    @raises(ValueError)
    def test_get_invalid_project_template(self):
        """Test that getting an invalid template throws an error."""
        fake_templates = [{'id': 'foo'}]
        user_info = dict(templates=fake_templates)
        project_info = dict(template_id='bar')
        UserFactory.create(info=user_info)
        project = ProjectFactory(info=project_info)
        self.base_analyst.get_project_template(project)

    @with_context
    @raises(ValueError)
    def test_get_non_existant_project_template(self):
        """Test that getting a non-existant template throws an error."""
        project = ProjectFactory()
        self.base_analyst.get_project_template(project)

    def test_dataframe_keys_replaced(self):
        """Test that dataframe keys are replaced and columns merged."""
        data = [{'foo': '你好', 'baz': 'qux'}, {'foo': 1, 'quux': 'qux'}]
        old_df = pandas.DataFrame(data, range(len(data)))
        new_df = self.base_analyst.replace_df_keys(old_df, quux='baz')
        assert_dict_equal(new_df.to_dict(), {
            'foo': {
                0: '你好',
                1: 1
            },
            'baz': {
                0: 'qux',
                1: 'qux'
            }
        })

    @with_context
    @patch('pybossa_lc.analysis.base.send_mail')
    @patch('pybossa_lc.analysis.base.render_template')
    @patch('pybossa_lc.analysis.base.Queue.enqueue')
    def test_comment_annotations_emailed(self, mock_enqueue, mock_render,
                                         mock_send_mail):
        """Test that comment annotation emails are sent."""
        mock_render.return_value = True
        comment = 'foo'
        creator = 'bar'
        target = 'example.com'
        fake_anno = {
            'creator': {
                'id': 'example.com/user1',
                'type': 'Person',
                'name': creator,
                'nickname': 'nick'
            },
            'body': {
                'type': 'TextualBody',
                'purpose': 'commenting',
                'value': comment,
                'format': 'text/plain'
            }
        }
        task = self.ctx.create_task(1, target)
        json_anno = json.dumps(fake_anno, indent=2, sort_keys=True)
        self.base_analyst.email_comment_anno(task, fake_anno)

        expected_render_args = [
            call('/account/email/new_comment_anno.md',
                 annotation=json_anno,
                 creator=creator,
                 comment=comment,
                 raw_image=None,
                 link=None),
            call('/account/email/new_comment_anno.html',
                 annotation=json_anno,
                 creator=creator,
                 comment=comment,
                 raw_image=None,
                 link=None)
        ]
        assert_equal(mock_render.call_args_list, expected_render_args)

        expected_msg = {
            'body': True,
            'html': True,
            'subject': 'New Comment Annotation',
            'recipients': flask_app.config.get('ADMINS')
        }
        mock_enqueue.assert_called_once_with(mock_send_mail, expected_msg)

    @with_context
    @patch('pybossa_lc.model.base.wa_client')
    def test_modified_results_not_updated(self, mock_client):
        """Test results are not updated if an Annotation has been modified."""
        task = self.ctx.create_task(1)
        TaskRunFactory(task=task)
        result = self.result_repo.get_by(task_id=task.id)
        self.base_analyst.analyse(result.id)
        mock_client.search_annotations.return_value = [{
            'modified': 'fake-time'
        }]
        assert_equal(mock_client.create_annotation.called, False)

    @with_context
    @patch('pybossa_lc.model.base.wa_client')
    def test_parent_results_not_updated(self, mock_client):
        """Test results are not updated if an Annotation has children."""
        task = self.ctx.create_task(1)
        TaskRunFactory(task=task)
        result = self.result_repo.get_by(task_id=task.id)
        result.info = dict(has_children=True)
        self.result_repo.update(result)
        self.base_analyst.analyse(result.id)
        assert_equal(mock_client.create_annotation.called, False)

    @with_context
    @patch('pybossa_lc.model.base.wa_client')
    def test_result_with_child_not_updated(self, mock_client):
        """Test that a result is not updated when it has a child."""
        task = self.ctx.create_task(1)
        TaskRunFactory(task=task)
        result = self.result_repo.get_by(task_id=task.id)
        info = dict(annotations='foo', has_children=True)
        result.info = info
        self.result_repo.update(result)
        self.base_analyst.analyse(result.id)
        assert_equal(result.info, info)

    @with_context
    def test_analysis_exception_if_no_annotation_collection(self):
        """Test that AnnotationCollection must be setup."""
        task = self.ctx.create_task(1, 'example.com', anno_collection=None)
        TaskRunFactory.create(task=task)
        result = self.result_repo.filter_by(project_id=task.project_id)[0]
        assert_raises(AnalysisException, self.base_analyst.analyse, result.id)
コード例 #21
0
class TestResultRepository(Test):
    def setUp(self):
        super(TestResultRepository, self).setUp()
        self.result_repo = ResultRepository(db)

    def create_result(self, n_answers=1, filter_by=False):
        task = TaskFactory.create(n_answers=n_answers)
        TaskRunFactory.create(task=task)
        if filter_by:
            return self.result_repo.filter_by(project_id=1)
        else:
            return self.result_repo.get_by(project_id=1)

    def test_get_return_none_if_no_result(self):
        """Test get method returns None if there is no result with the
        specified id"""

        result = self.result_repo.get(2)

        assert result is None, result

    def test_get_returns_result(self):
        """Test get method returns a result if exists"""

        n_answers = 1

        task = TaskFactory.create(n_answers=n_answers)
        task_run = TaskRunFactory.create(task=task)

        result = self.result_repo.filter_by(project_id=1)

        err_msg = "There should be a result"
        assert len(result) == 1, err_msg
        result = result[0]
        assert result.project_id == 1, err_msg
        assert result.task_id == task.id, err_msg
        assert len(result.task_run_ids) == n_answers, err_msg
        err_msg = "The task_run id is missing in the results array"
        for tr_id in result.task_run_ids:
            assert tr_id == task_run.id, err_msg

    def test_get_by_returns_result(self):
        """Test get_by method returns a result if exists"""

        n_answers = 1

        task = TaskFactory.create(n_answers=n_answers)
        task_run = TaskRunFactory.create(task=task)

        result = self.result_repo.get_by(project_id=1)

        err_msg = "There should be a result"
        assert result.project_id == 1, err_msg
        assert result.task_id == task.id, err_msg
        assert len(result.task_run_ids) == n_answers, err_msg
        err_msg = "The task_run id is missing in the results array"
        for tr_id in result.task_run_ids:
            assert tr_id == task_run.id, err_msg

    def test_get_returns_result_after_increasig_redundancy(self):
        """Test get method returns a result if after increasing redundancy"""

        n_answers = 1

        task = TaskFactory.create(n_answers=n_answers)
        task_run = TaskRunFactory.create(task=task)

        result = self.result_repo.filter_by(project_id=1)

        err_msg = "There should be a result"
        assert len(result) == 1, err_msg
        result = result[0]
        assert result.project_id == 1, err_msg
        assert result.task_id == task.id, err_msg
        assert len(result.task_run_ids) == n_answers, err_msg
        err_msg = "The task_run id is missing in the results array"
        for tr_id in result.task_run_ids:
            assert tr_id == task_run.id, err_msg

        # Increase redundancy
        tmp = task_repo.get_task(task.id)
        tmp.n_answers = 2
        task_repo.update(task)

        err_msg = "There should be only one result"
        results = result_repo.filter_by(project_id=1)
        assert len(results) == 1, err_msg
        task_run_2 = TaskRunFactory.create(task=task)

        err_msg = "There should be 1 results"
        results = result_repo.filter_by(project_id=1)
        assert len(results) == 1, err_msg

        err_msg = "There should be 2 results"
        results = result_repo.filter_by(project_id=1, last_version=False)
        assert len(results) == 2, err_msg

        assert results[1].project_id == 1, err_msg
        assert results[1].task_id == task.id, err_msg
        err_msg = "First result should have only one task run ID"
        assert len(results[0].task_run_ids) == 1, err_msg
        err_msg = "Second result should have only two task run IDs"
        assert len(results[1].task_run_ids) == 2, err_msg
        err_msg = "The task_run id is missing in the results array"
        for tr_id in results[1].task_run_ids:
            assert tr_id in [task_run.id, task_run_2.id], err_msg

    def test_get_returns_no_result(self):
        """Test get method does not return a result if task not completed"""

        n_answers = 3

        task = TaskFactory.create(n_answers=n_answers)
        TaskRunFactory.create(task=task)

        result = self.result_repo.filter_by(project_id=1)

        err_msg = "There should not be a result"
        assert len(result) == 0, err_msg

    def test_fulltext_search_result(self):
        """Test fulltext search in JSON info works."""
        result = self.create_result()
        text = 'something word you me bar'
        data = {'foo': text}
        result.info = data
        self.result_repo.update(result)

        info = 'foo::word'
        res = self.result_repo.filter_by(info=info, fulltextsearch='1')
        assert len(res) == 1, len(res)
        assert res[0].info['foo'] == text, res[0]

        res = self.result_repo.filter_by(info=info)
        assert len(res) == 0, len(res)

    def test_fulltext_search_result_01(self):
        """Test fulltext search in JSON info works."""
        result = self.create_result()
        text = 'something word you me bar'
        data = {'foo': text, 'bar': 'foo'}
        result.info = data
        self.result_repo.update(result)

        info = 'foo::word&bar|bar::foo'
        res = self.result_repo.filter_by(info=info, fulltextsearch='1')
        assert len(res) == 1, len(res)
        assert res[0].info['foo'] == text, res[0]

    def test_info_json_search_result(self):
        """Test search in JSON info works."""
        result = self.create_result()
        text = 'bar'
        data = {'foo': text}
        result.info = data
        self.result_repo.update(result)

        info = 'foo::bar'
        res = self.result_repo.filter_by(info=info)
        assert len(res) == 1, len(res)
        assert res[0].info['foo'] == text, res[0]

    def test_update(self):
        """Test update persists the changes made to the result"""

        result = self.create_result()
        result.info = dict(new='value')

        self.result_repo.update(result)
        updated_result = self.result_repo.get(result.id)

        assert updated_result.info['new'] == 'value', updated_result

    def test_update_fails_if_integrity_error(self):
        """Test update raises a DBIntegrityError if the instance to be updated
        lacks a required value"""

        result = self.create_result()
        result.project_id = None

        assert_raises(DBIntegrityError, self.result_repo.update, result)

    def test_update_only_updates_results(self):
        """Test update raises a WrongObjectError when an object which is not
        a Result instance is updated"""

        bad_object = dict()

        assert_raises(WrongObjectError, self.result_repo.update, bad_object)
コード例 #22
0
class TestResultAuthorization(Test):

    mock_anonymous = mock_current_user()
    mock_authenticated = mock_current_user(anonymous=False, admin=False, id=2)
    mock_pro = mock_current_user(anonymous=False, admin=False, id=2, pro=True)
    mock_admin = mock_current_user(anonymous=False, admin=True, id=1)
    mock_owner = mock_current_user(anonymous=False, admin=False, id=1)

    def setUp(self):
        super(TestResultAuthorization, self).setUp()
        self.result_repo = ResultRepository(db)

    def create_result(self, n_answers=1, filter_by=False):
        task = TaskFactory.create(n_answers=n_answers)
        TaskRunFactory.create(task=task)
        if filter_by:
            return self.result_repo.filter_by(project_id=1)
        else:
            return self.result_repo.get_by(project_id=1)


    @patch('pybossa.auth.current_user', new=mock_anonymous)
    def test_anonymous_user_can_read_result(self):
        """Test anonymous users can read results"""

        result = self.create_result()

        assert ensure_authorized_to('read', result)

    @patch('pybossa.auth.current_user', new=mock_authenticated)
    def test_auth_user_can_read_result(self):
        """Test auth users can read results"""

        result = self.create_result()

        assert ensure_authorized_to('read', result)

    @patch('pybossa.auth.current_user', new=mock_admin)
    def test_admin_user_can_read_result(self):
        """Test admin users can read results"""

        result = self.create_result()

        assert ensure_authorized_to('read', result)


    @patch('pybossa.auth.current_user', new=mock_anonymous)
    def test_anonymous_user_cannot_save_results(self):
        """Test anonymous users cannot save results of a specific project"""

        result = Result()

        assert_raises(Unauthorized, ensure_authorized_to, 'create', result)

    @patch('pybossa.auth.current_user', new=mock_authenticated)
    def test_authenticated_user_cannot_save_results(self):
        """Test authenticated users cannot save results of a specific project"""

        result = Result()

        assert_raises(Forbidden, ensure_authorized_to, 'create', result)


    @patch('pybossa.auth.current_user', new=mock_admin)
    def test_admin_user_cannot_save_results(self):
        """Test admin users cannot save results of a specific project"""

        result = Result()

        assert_raises(Forbidden, ensure_authorized_to, 'create', result)

    @patch('pybossa.auth.current_user', new=mock_anonymous)
    def test_anonymous_user_cannot_delete_results(self):
        """Test anonymous users cannot delete results of a specific project"""

        result = Result()

        assert_raises(Unauthorized, ensure_authorized_to, 'delete', result)

    @patch('pybossa.auth.current_user', new=mock_authenticated)
    def test_authenticated_user_cannot_delete_results(self):
        """Test authenticated users cannot delete results of a specific project"""

        result = Result()

        assert_raises(Forbidden, ensure_authorized_to, 'delete', result)


    @patch('pybossa.auth.current_user', new=mock_admin)
    def test_admin_user_cannot_delete_results(self):
        """Test admin users cannot delete results of a specific project"""

        result = Result()

        assert_raises(Forbidden, ensure_authorized_to, 'delete', result)

    @patch('pybossa.auth.current_user', new=mock_anonymous)
    def test_anonymous_user_cannot_update_results(self):
        """Test anonymous users cannot update results of a specific project"""

        result = self.create_result()

        assert_raises(Unauthorized, ensure_authorized_to, 'update', result)

    @patch('pybossa.auth.current_user', new=mock_authenticated)
    def test_auth_user_cannot_update_results(self):
        """Test auth users but not owner cannot update results of a specific project"""

        result = self.create_result()

        assert_raises(Forbidden, ensure_authorized_to, 'update', result)

    @patch('pybossa.auth.current_user', new=mock_owner)
    def test_auth_owner_can_update_results(self):
        """Test auth owner can update results of a specific project"""

        result = self.create_result()
        result.info = dict(new='value')

        assert ensure_authorized_to('update', result)

        updated_result = self.result_repo.get_by(id=result.id)

        err_msg = "The result has not been updated"
        assert updated_result.info['new'] == 'value', err_msg
コード例 #23
0
ファイル: test_task_api.py プロジェクト: sakho3600/pybossa
from default import db, with_context
from nose.tools import assert_equal
from test_api import TestAPI
from mock import patch, call

from factories import ProjectFactory, TaskFactory, TaskRunFactory, UserFactory
from factories import AnonymousTaskRunFactory, ExternalUidTaskRunFactory

from pybossa.repositories import ProjectRepository
from pybossa.repositories import TaskRepository
from pybossa.repositories import ResultRepository
from pybossa.model.counter import Counter

project_repo = ProjectRepository(db)
task_repo = TaskRepository(db)
result_repo = ResultRepository(db)


class TestTaskAPI(TestAPI):
    def create_result(self,
                      n_results=1,
                      n_answers=1,
                      owner=None,
                      filter_by=False):
        if owner:
            owner = owner
        else:
            admin, owner, user = UserFactory.create_batch(3)
        project = ProjectFactory.create(owner=owner)
        tasks = []
        for i in range(n_results):
コード例 #24
0
class TestResultRepository(Test):

    def setUp(self):
        super(TestResultRepository, self).setUp()
        self.result_repo = ResultRepository(db)

    def create_result(self, n_answers=1, filter_by=False):
        task = TaskFactory.create(n_answers=n_answers)
        TaskRunFactory.create(task=task)
        if filter_by:
            return self.result_repo.filter_by(project_id=1)
        else:
            return self.result_repo.get_by(project_id=1)


    def test_get_return_none_if_no_result(self):
        """Test get method returns None if there is no result with the
        specified id"""

        result = self.result_repo.get(2)

        assert result is None, result


    def test_get_returns_result(self):
        """Test get method returns a result if exists"""

        n_answers = 1

        task = TaskFactory.create(n_answers=n_answers)
        task_run = TaskRunFactory.create(task=task)

        result = self.result_repo.filter_by(project_id=1)


        err_msg = "There should be a result"
        assert len(result) == 1, err_msg
        result = result[0]
        assert result.project_id == 1, err_msg
        assert result.task_id == task.id, err_msg
        assert len(result.task_run_ids) == n_answers, err_msg
        err_msg = "The task_run id is missing in the results array"
        for tr_id in result.task_run_ids:
            assert tr_id == task_run.id, err_msg

    def test_get_by_returns_result(self):
        """Test get_by method returns a result if exists"""

        n_answers = 1

        task = TaskFactory.create(n_answers=n_answers)
        task_run = TaskRunFactory.create(task=task)

        result = self.result_repo.get_by(project_id=1)


        err_msg = "There should be a result"
        assert result.project_id == 1, err_msg
        assert result.task_id == task.id, err_msg
        assert len(result.task_run_ids) == n_answers, err_msg
        err_msg = "The task_run id is missing in the results array"
        for tr_id in result.task_run_ids:
            assert tr_id == task_run.id, err_msg


    def test_get_returns_result_after_increasig_redundancy(self):
        """Test get method returns a result if after increasing redundancy"""

        n_answers = 1

        task = TaskFactory.create(n_answers=n_answers)
        task_run = TaskRunFactory.create(task=task)

        result = self.result_repo.filter_by(project_id=1)

        err_msg = "There should be a result"
        assert len(result) == 1, err_msg
        result = result[0]
        assert result.project_id == 1, err_msg
        assert result.task_id == task.id, err_msg
        assert len(result.task_run_ids) == n_answers, err_msg
        err_msg = "The task_run id is missing in the results array"
        for tr_id in result.task_run_ids:
            assert tr_id == task_run.id, err_msg

        # Increase redundancy
        tmp = task_repo.get_task(task.id)
        tmp.n_answers = 2
        task_repo.update(task)

        err_msg = "There should be only one result"
        results = result_repo.filter_by(project_id=1)
        assert len(results) == 1, err_msg
        task_run_2 = TaskRunFactory.create(task=task)

        err_msg = "There should be 1 results"
        results = result_repo.filter_by(project_id=1)
        assert len(results) == 1, err_msg

        err_msg = "There should be 2 results"
        results = result_repo.filter_by(project_id=1, last_version=False)
        assert len(results) == 2, err_msg

        assert results[1].project_id == 1, err_msg
        assert results[1].task_id == task.id, err_msg
        err_msg = "First result should have only one task run ID"
        assert len(results[0].task_run_ids) == 1, err_msg
        err_msg = "Second result should have only two task run IDs"
        assert len(results[1].task_run_ids) == 2, err_msg
        err_msg = "The task_run id is missing in the results array"
        for tr_id in results[1].task_run_ids:
            assert tr_id in [task_run.id, task_run_2.id], err_msg


    def test_get_returns_no_result(self):
        """Test get method does not return a result if task not completed"""

        n_answers = 3

        task = TaskFactory.create(n_answers=n_answers)
        TaskRunFactory.create(task=task)

        result = self.result_repo.filter_by(project_id=1)

        err_msg = "There should not be a result"
        assert len(result) == 0, err_msg

    def test_update(self):
        """Test update persists the changes made to the result"""

        result = self.create_result()
        result.info = dict(new='value')

        self.result_repo.update(result)
        updated_result = self.result_repo.get(result.id)

        assert updated_result.info['new'] == 'value', updated_result


    def test_update_fails_if_integrity_error(self):
        """Test update raises a DBIntegrityError if the instance to be updated
        lacks a required value"""

        result = self.create_result()
        result.project_id = None

        assert_raises(DBIntegrityError, self.result_repo.update, result)


    def test_update_only_updates_results(self):
        """Test update raises a WrongObjectError when an object which is not
        a Result instance is updated"""

        bad_object = dict()

        assert_raises(WrongObjectError, self.result_repo.update, bad_object)