Esempio n. 1
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)
Esempio n. 2
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
Esempio n. 3
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
    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
Esempio n. 4
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
Esempio n. 5
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
    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
Esempio n. 6
0
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
        })
Esempio n. 7
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)
Esempio n. 8
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
Esempio n. 9
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]
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)
Esempio n. 11
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
        })
Esempio n. 12
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)