Beispiel #1
0
class TestTaskRepositorySaveDeleteUpdate(Test):
    def setUp(self):
        super(TestTaskRepositorySaveDeleteUpdate, self).setUp()
        self.task_repo = TaskRepository(db)

    def test_save_saves_tasks(self):
        """Test save persists Task instances"""

        task = TaskFactory.build()
        assert self.task_repo.get_task(task.id) is None

        self.task_repo.save(task)

        assert self.task_repo.get_task(task.id) == task, "Task not saved"

    def test_save_saves_taskruns(self):
        """Test save persists TaskRun instances"""

        taskrun = TaskRunFactory.build()
        assert self.task_repo.get_task_run(taskrun.id) is None

        self.task_repo.save(taskrun)

        assert self.task_repo.get_task_run(
            taskrun.id) == taskrun, "TaskRun not saved"

    def test_save_fails_if_integrity_error(self):
        """Test save raises a DBIntegrityError if the instance to be saved lacks
        a required value"""

        task = TaskFactory.build(project_id=None, project=None)

        assert_raises(DBIntegrityError, self.task_repo.save, task)

    def test_save_only_saves_tasks_and_taskruns(self):
        """Test save raises a WrongObjectError when an object which is neither
        a Task nor a Taskrun instance is saved"""

        bad_object = dict()

        assert_raises(WrongObjectError, self.task_repo.save, bad_object)

    def test_update_task(self):
        """Test update persists the changes made to Task instances"""

        task = TaskFactory.create(state='ongoing')
        task.state = 'done'

        self.task_repo.update(task)
        updated_task = self.task_repo.get_task(task.id)

        assert updated_task.state == 'done', updated_task

    def test_update_taskrun(self):
        """Test update persists the changes made to TaskRun instances"""

        taskrun = TaskRunFactory.create(info='info')
        taskrun.info = 'updated info!'

        self.task_repo.update(taskrun)
        updated_taskrun = self.task_repo.get_task_run(taskrun.id)

        assert updated_taskrun.info == 'updated info!', updated_taskrun

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

        task = TaskFactory.create()
        task.project_id = None

        assert_raises(DBIntegrityError, self.task_repo.update, task)

    def test_update_only_updates_tasks_and_taskruns(self):
        """Test update raises a WrongObjectError when an object which is neither
        a Task nor a TaskRun instance is updated"""

        bad_object = dict()

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

    def test_delete_task(self):
        """Test delete removes the Task instance"""

        task = TaskFactory.create()

        self.task_repo.delete(task)
        deleted = self.task_repo.get_task(task.id)

        assert deleted is None, deleted

    def test_delete_task_deletes_dependent_taskruns(self):
        """Test delete removes the dependent TaskRun instances"""

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

        self.task_repo.delete(task)
        deleted = self.task_repo.get_task_run(taskrun.id)

        assert deleted is None, deleted

    def test_delete_taskrun(self):
        """Test delete removes the TaskRun instance"""

        taskrun = TaskRunFactory.create()

        self.task_repo.delete(taskrun)
        deleted = self.task_repo.get_task_run(taskrun.id)

        assert deleted is None, deleted

    def test_delete_only_deletes_tasks(self):
        """Test delete raises a WrongObjectError if is requested to delete other
        than a task"""

        bad_object = dict()

        assert_raises(WrongObjectError, self.task_repo.delete, bad_object)

    def test_delete_all_deletes_many_tasks(self):
        """Test delete_all deletes many tasks at once"""

        tasks = TaskFactory.create_batch(2)

        self.task_repo.delete_all(tasks)

        for task in tasks:
            assert self.task_repo.get_task(task.id) is None, task

    def test_delete_all_deletes_dependent(self):
        """Test delete_all deletes dependent taskruns too"""

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

        self.task_repo.delete_all([task])
        deleted = self.task_repo.get_task_run(taskrun.id)

        assert deleted is None, deleted

    def test_delete_all_deletes_many_taskruns(self):
        """Test delete_all deletes many taskruns at once"""

        taskruns = TaskRunFactory.create_batch(2)

        self.task_repo.delete_all(taskruns)

        for taskrun in taskruns:
            assert self.task_repo.get_task_run(taskrun.id) is None, taskrun

    def test_delete_all_raises_error_if_no_task(self):
        """Test delete_all raises a WrongObjectError if is requested to delete
        any other object than a task"""

        bad_objects = [dict(), 'string']

        assert_raises(WrongObjectError, self.task_repo.delete_all, bad_objects)

    def test_update_tasks_redundancy_changes_all_project_tasks_redundancy(
            self):
        """Test update_tasks_redundancy updates the n_answers value for every
        task in the project"""

        project = ProjectFactory.create()
        TaskFactory.create_batch(2, project=project, n_answers=1)

        self.task_repo.update_tasks_redundancy(project, 2)
        tasks = self.task_repo.filter_tasks_by(project_id=project.id)

        for task in tasks:
            assert task.n_answers == 2, task.n_answers

    def test_update_tasks_redundancy_updates_state_when_incrementing(self):
        """Test update_tasks_redundancy changes 'completed' tasks to 'ongoing'
        if n_answers is incremented enough"""

        project = ProjectFactory.create()
        tasks = TaskFactory.create_batch(2, project=project, n_answers=2)
        TaskRunFactory.create_batch(2, task=tasks[0])
        tasks[0].state = 'completed'
        self.task_repo.update(tasks[0])

        assert tasks[0].state == 'completed', tasks[0].state
        assert tasks[1].state == 'ongoing', tasks[1].state

        self.task_repo.update_tasks_redundancy(project, 3)
        tasks = self.task_repo.filter_tasks_by(project_id=project.id)

        for task in tasks:
            assert task.state == 'ongoing', task.state

    def test_update_tasks_redundancy_updates_state_when_decrementing(self):
        """Test update_tasks_redundancy changes 'ongoing' tasks to 'completed'
        if n_answers is decremented enough"""

        project = ProjectFactory.create()
        tasks = TaskFactory.create_batch(2, project=project, n_answers=2)
        TaskRunFactory.create_batch(2, task=tasks[0])
        TaskRunFactory.create(task=tasks[1])
        tasks[0].state = 'completed'
        self.task_repo.update(tasks[0])

        assert tasks[0].state == 'completed', tasks[0].state
        assert tasks[1].state == 'ongoing', tasks[1].state

        self.task_repo.update_tasks_redundancy(project, 1)
        tasks = self.task_repo.filter_tasks_by(project_id=project.id)

        for task in tasks:
            assert task.state == 'completed', task.state
class TestTaskRepositoryForTaskQueries(Test):
    def setUp(self):
        super(TestTaskRepositoryForTaskQueries, self).setUp()
        self.task_repo = TaskRepository(db)

    def test_orderby(self):
        """Test orderby."""
        project = ProjectFactory.create()
        task1 = TaskFactory.create(fav_user_ids=[1], project=project)
        task2 = TaskFactory.create(fav_user_ids=None, project=project)
        task3 = TaskFactory.create(fav_user_ids=[1, 2, 3], project=project)

        task = self.task_repo.filter_tasks_by(orderby='id',
                                              desc=True,
                                              project_id=project.id,
                                              limit=1)[0]
        assert task == task3, (task, task3)

        task = self.task_repo.filter_tasks_by(orderby='id',
                                              desc=False,
                                              project_id=project.id,
                                              limit=1)[0]
        assert task == task1, (task, task1)

        task = self.task_repo.filter_tasks_by(orderby='created',
                                              desc=True,
                                              project_id=project.id)[0]
        assert task == task3, (task.id, task3.id)

        task = self.task_repo.filter_tasks_by(orderby='created',
                                              desc=False,
                                              project_id=project.id)[0]
        assert task == task1, (task.created, task1.created)

        task = self.task_repo.filter_tasks_by(orderby='fav_user_ids',
                                              desc=True,
                                              project_id=project.id)[0][0]
        assert task == task3, (task.id, task3.id)

        task = self.task_repo.filter_tasks_by(orderby='fav_user_ids',
                                              desc=False,
                                              project_id=project.id)[0][0]
        assert task == task2, (task.fav_user_ids, task2.fav_user_ids)

    def test_handle_info_json_plain_text(self):
        """Test handle info in JSON as plain text works."""
        TaskFactory.create(info='answer')
        res = self.task_repo.filter_tasks_by(info='answer')
        assert len(res) == 1
        assert res[0].info == 'answer', res[0]

    def test_handle_info_json(self):
        """Test handle info in JSON works."""
        TaskFactory.create(info={'foo': 'bar'})
        info = 'foo::bar'
        res = self.task_repo.filter_tasks_by(info=info)
        assert len(res) == 1
        assert res[0].info['foo'] == 'bar', res[0]

    def test_handle_info_json_fulltextsearch(self):
        """Test handle info fulltextsearch in JSON works."""
        text = 'bar word agent something'
        TaskFactory.create(info={'foo': text})
        info = 'foo::agent'
        res = self.task_repo.filter_tasks_by(info=info, fulltextsearch='1')
        assert len(res) == 1
        assert res[0][0].info['foo'] == text, res[0]

        res = self.task_repo.filter_tasks_by(info=info)
        assert len(res) == 0, len(res)

    def test_handle_info_json_multiple_keys(self):
        """Test handle info in JSON with multiple keys works."""
        TaskFactory.create(info={'foo': 'bar', 'bar': 'foo'})
        info = 'foo::bar|bar::foo'
        res = self.task_repo.filter_tasks_by(info=info)
        assert len(res) == 1
        assert res[0].info['foo'] == 'bar', res[0]
        assert res[0].info['bar'] == 'foo', res[0]

    def test_handle_info_json_multiple_keys_fulltextsearch(self):
        """Test handle info in JSON with multiple keys works."""
        text = "two three four five"
        TaskFactory.create(info={'foo': 'bar', 'extra': text})
        info = 'foo::bar|extra::four'
        res = self.task_repo.filter_tasks_by(info=info, fulltextsearch='1')
        assert len(res) == 1, len(res)
        assert res[0][0].info['foo'] == 'bar', res[0]
        assert res[0][0].info['extra'] == text, res[0]

        res = self.task_repo.filter_tasks_by(info=info)
        assert len(res) == 0, len(res)

    def test_handle_info_json_multiple_keys_and_fulltextsearch(self):
        """Test handle info in JSON with multiple keys and AND operator works."""
        text = "agent myself you bar"
        TaskFactory.create(info={'foo': 'bar', 'bar': text})

        info = 'foo::bar|bar::you&agent'
        res = self.task_repo.filter_tasks_by(info=info, fulltextsearch='1')
        assert len(res) == 1, len(res)
        assert res[0][0].info['foo'] == 'bar', res[0]
        assert res[0][0].info['bar'] == text, res[0]

    def test_handle_info_json_multiple_keys_404(self):
        """Test handle info in JSON with multiple keys not found works."""
        TaskFactory.create(info={'foo': 'bar', 'daniel': 'foo'})
        info = 'foo::bar|bar::foo'
        res = self.task_repo.filter_tasks_by(info=info)
        assert len(res) == 0

    def test_handle_info_json_multiple_keys_404_with_one_pipe(self):
        """Test handle info in JSON with multiple keys not found works."""
        TaskFactory.create(info={'foo': 'bar', 'bar': 'foo'})
        info = 'foo::bar|'
        res = self.task_repo.filter_tasks_by(info=info)
        assert len(res) == 1
        assert res[0].info['foo'] == 'bar', res[0]
        assert res[0].info['bar'] == 'foo', res[0]

    def test_handle_info_json_multiple_keys_404_fulltextsearch(self):
        """Test handle info in JSON with full text
        search with multiple keys not found works."""
        TaskFactory.create(info={'foo': 'bar', 'bar': 'foo'})
        info = 'foo::bar|'
        res = self.task_repo.filter_tasks_by(info=info, fulltextsearch='1')
        assert len(res) == 1
        assert res[0][0].info['foo'] == 'bar', res[0]
        assert res[0][0].info['bar'] == 'foo', res[0]

    def test_handle_info_json_wrong_data(self):
        """Test handle info in JSON with wrong data works."""
        TaskFactory.create(info={'foo': 'bar', 'bar': 'foo'})

        infos = ['|', '||', '|::', ':|', '::|', '|:', 'foo|', 'foo|']
        for info in infos:
            res = self.task_repo.filter_tasks_by(info=info)
            assert len(res) == 0

        for info in infos:
            res = self.task_repo.filter_tasks_by(info=info, fulltextsearch='1')
            assert len(res) == 0

    def test_get_task_return_none_if_no_task(self):
        """Test get_task method returns None if there is no task with the
        specified id"""

        task = self.task_repo.get_task(200)

        assert task is None, task

    def test_get_task_returns_task(self):
        """Test get_task method returns a task if exists"""

        task = TaskFactory.create()

        retrieved_task = self.task_repo.get_task(task.id)

        assert task == retrieved_task, retrieved_task

    def test_get_task_by(self):
        """Test get_task_by returns a task with the specified attribute"""

        task = TaskFactory.create(state='done')

        retrieved_task = self.task_repo.get_task_by(state=task.state)

        assert task == retrieved_task, retrieved_task

    def test_get_task_by_returns_none_if_no_task(self):
        """Test get_task_by returns None if no task matches the query"""

        TaskFactory.create(state='done')

        task = self.task_repo.get_task_by(state='ongoing')

        assert task is None, task

    def test_filter_tasks_by_no_matches(self):
        """Test filter_tasks_by returns an empty list if no tasks match the query"""

        TaskFactory.create(state='done', n_answers=17)

        retrieved_tasks = self.task_repo.filter_tasks_by(state='ongoing')

        assert isinstance(retrieved_tasks, list)
        assert len(retrieved_tasks) == 0, retrieved_tasks

    def test_filter_tasks_by_one_condition(self):
        """Test filter_tasks_by returns a list of tasks that meet the filtering
        condition"""

        TaskFactory.create_batch(3, state='done')
        should_be_missing = TaskFactory.create(state='ongoing')

        retrieved_tasks = self.task_repo.filter_tasks_by(state='done')

        assert len(retrieved_tasks) == 3, retrieved_tasks
        assert should_be_missing not in retrieved_tasks, retrieved_tasks

    def test_filter_tasks_by_multiple_conditions(self):
        """Test filter_tasks_by supports multiple-condition queries"""

        TaskFactory.create(state='done', n_answers=17)
        task = TaskFactory.create(state='done', n_answers=99)

        retrieved_tasks = self.task_repo.filter_tasks_by(state='done',
                                                         n_answers=99)

        assert len(retrieved_tasks) == 1, retrieved_tasks
        assert task in retrieved_tasks, retrieved_tasks

    def test_filter_tasks_support_yield_option(self):
        """Test that filter_tasks_by with the yielded=True option returns the
        results as a generator"""

        tasks = TaskFactory.create_batch(2, state='done')

        yielded_tasks = self.task_repo.filter_tasks_by(state='done',
                                                       yielded=True)

        import types
        assert isinstance(yielded_tasks.__iter__(), types.GeneratorType)
        for task in yielded_tasks:
            assert task in tasks

    def test_filter_tasks_limit_offset(self):
        """Test that filter_tasks_by supports limit and offset options"""

        TaskFactory.create_batch(4)
        all_tasks = self.task_repo.filter_tasks_by()

        first_two = self.task_repo.filter_tasks_by(limit=2)
        last_two = self.task_repo.filter_tasks_by(limit=2, offset=2)

        assert len(first_two) == 2, first_two
        assert len(last_two) == 2, last_two
        assert first_two == all_tasks[:2]
        assert last_two == all_tasks[2:]

    def test_count_tasks_with_no_matches(self):
        """Test count_tasks_with returns 0 if no tasks match the query"""

        TaskFactory.create(state='done', n_answers=17)

        count = self.task_repo.count_tasks_with(state='ongoing')

        assert count == 0, count

    def test_count_tasks_with_one_condition(self):
        """Test count_tasks_with returns the number of tasks that meet the
        filtering condition"""

        TaskFactory.create_batch(3, state='done')
        should_be_missing = TaskFactory.create(state='ongoing')

        count = self.task_repo.count_tasks_with(state='done')

        assert count == 3, count

    def test_count_tasks_with_multiple_conditions(self):
        """Test count_tasks_with supports multiple-condition queries"""

        TaskFactory.create(state='done', n_answers=17)
        task = TaskFactory.create(state='done', n_answers=99)

        count = self.task_repo.count_tasks_with(state='done', n_answers=99)

        assert count == 1, count
Beispiel #3
0
class TestTaskRepositoryForTaskQueries(Test):
    def setUp(self):
        super(TestTaskRepositoryForTaskQueries, self).setUp()
        self.task_repo = TaskRepository(db)

    def test_get_task_return_none_if_no_task(self):
        """Test get_task method returns None if there is no task with the
        specified id"""

        task = self.task_repo.get_task(200)

        assert task is None, task

    def test_get_task_returns_task(self):
        """Test get_task method returns a task if exists"""

        task = TaskFactory.create()

        retrieved_task = self.task_repo.get_task(task.id)

        assert task == retrieved_task, retrieved_task

    def test_get_task_by(self):
        """Test get_task_by returns a task with the specified attribute"""

        task = TaskFactory.create(state='done')

        retrieved_task = self.task_repo.get_task_by(state=task.state)

        assert task == retrieved_task, retrieved_task

    def test_get_task_by_returns_none_if_no_task(self):
        """Test get_task_by returns None if no task matches the query"""

        TaskFactory.create(state='done')

        task = self.task_repo.get_task_by(state='ongoing')

        assert task is None, task

    def test_filter_tasks_by_no_matches(self):
        """Test filter_tasks_by returns an empty list if no tasks match the query"""

        TaskFactory.create(state='done', n_answers=17)

        retrieved_tasks = self.task_repo.filter_tasks_by(state='ongoing')

        assert isinstance(retrieved_tasks, list)
        assert len(retrieved_tasks) == 0, retrieved_tasks

    def test_filter_tasks_by_one_condition(self):
        """Test filter_tasks_by returns a list of tasks that meet the filtering
        condition"""

        TaskFactory.create_batch(3, state='done')
        should_be_missing = TaskFactory.create(state='ongoing')

        retrieved_tasks = self.task_repo.filter_tasks_by(state='done')

        assert len(retrieved_tasks) == 3, retrieved_tasks
        assert should_be_missing not in retrieved_tasks, retrieved_tasks

    def test_filter_tasks_by_multiple_conditions(self):
        """Test filter_tasks_by supports multiple-condition queries"""

        TaskFactory.create(state='done', n_answers=17)
        task = TaskFactory.create(state='done', n_answers=99)

        retrieved_tasks = self.task_repo.filter_tasks_by(state='done',
                                                         n_answers=99)

        assert len(retrieved_tasks) == 1, retrieved_tasks
        assert task in retrieved_tasks, retrieved_tasks

    def test_filter_tasks_support_yield_option(self):
        """Test that filter_tasks_by with the yielded=True option returns the
        results as a generator"""

        tasks = TaskFactory.create_batch(2, state='done')

        yielded_tasks = self.task_repo.filter_tasks_by(state='done',
                                                       yielded=True)

        import types
        assert isinstance(yielded_tasks.__iter__(), types.GeneratorType)
        for task in yielded_tasks:
            assert task in tasks

    def test_filter_tasks_limit_offset(self):
        """Test that filter_tasks_by supports limit and offset options"""

        TaskFactory.create_batch(4)
        all_tasks = self.task_repo.filter_tasks_by()

        first_two = self.task_repo.filter_tasks_by(limit=2)
        last_two = self.task_repo.filter_tasks_by(limit=2, offset=2)

        assert len(first_two) == 2, first_two
        assert len(last_two) == 2, last_two
        assert first_two == all_tasks[:2]
        assert last_two == all_tasks[2:]

    def test_count_tasks_with_no_matches(self):
        """Test count_tasks_with returns 0 if no tasks match the query"""

        TaskFactory.create(state='done', n_answers=17)

        count = self.task_repo.count_tasks_with(state='ongoing')

        assert count == 0, count

    def test_count_tasks_with_one_condition(self):
        """Test count_tasks_with returns the number of tasks that meet the
        filtering condition"""

        TaskFactory.create_batch(3, state='done')
        should_be_missing = TaskFactory.create(state='ongoing')

        count = self.task_repo.count_tasks_with(state='done')

        assert count == 3, count

    def test_count_tasks_with_multiple_conditions(self):
        """Test count_tasks_with supports multiple-condition queries"""

        TaskFactory.create(state='done', n_answers=17)
        task = TaskFactory.create(state='done', n_answers=99)

        count = self.task_repo.count_tasks_with(state='done', n_answers=99)

        assert count == 1, count
class TestTaskRepositoryForTaskQueries(Test):

    def setUp(self):
        super(TestTaskRepositoryForTaskQueries, self).setUp()
        self.task_repo = TaskRepository(db)


    def test_handle_info_json_plain_text(self):
        """Test handle info in JSON as plain text works."""
        TaskFactory.create(info='answer')
        res = self.task_repo.filter_tasks_by(info='answer')
        assert len(res) == 1
        assert res[0].info == 'answer', res[0]

    def test_handle_info_json(self):
        """Test handle info in JSON works."""
        TaskFactory.create(info={'foo': 'bar'})
        info = 'foo::bar'
        res = self.task_repo.filter_tasks_by(info=info)
        assert len(res) == 1
        assert res[0].info['foo'] == 'bar', res[0]

    def test_handle_info_json_fulltextsearch(self):
        """Test handle info fulltextsearch in JSON works."""
        text = 'bar word agent something'
        TaskFactory.create(info={'foo': text})
        info = 'foo::agent'
        res = self.task_repo.filter_tasks_by(info=info, fulltextsearch='1')
        assert len(res) == 1
        assert res[0].info['foo'] == text, res[0]

        res = self.task_repo.filter_tasks_by(info=info)
        assert len(res) == 0, len(res)


    def test_handle_info_json_multiple_keys(self):
        """Test handle info in JSON with multiple keys works."""
        TaskFactory.create(info={'foo': 'bar', 'bar': 'foo'})
        info = 'foo::bar|bar::foo'
        res = self.task_repo.filter_tasks_by(info=info)
        assert len(res) == 1
        assert res[0].info['foo'] == 'bar', res[0]
        assert res[0].info['bar'] == 'foo', res[0]

    def test_handle_info_json_multiple_keys_fulltextsearch(self):
        """Test handle info in JSON with multiple keys works."""
        text = "two three four five"
        TaskFactory.create(info={'foo': 'bar', 'extra': text})
        info = 'foo::bar|extra::four'
        res = self.task_repo.filter_tasks_by(info=info, fulltextsearch='1')
        assert len(res) == 1, len(res)
        assert res[0].info['foo'] == 'bar', res[0]
        assert res[0].info['extra'] == text, res[0]

        res = self.task_repo.filter_tasks_by(info=info)
        assert len(res) == 0, len(res)

    def test_handle_info_json_multiple_keys_and_fulltextsearch(self):
        """Test handle info in JSON with multiple keys and AND operator works."""
        text = "agent myself you bar"
        TaskFactory.create(info={'foo': 'bar', 'bar': text})

        info = 'foo::bar|bar::you&agent'
        res = self.task_repo.filter_tasks_by(info=info, fulltextsearch='1')
        assert len(res) == 1, len(res)
        assert res[0].info['foo'] == 'bar', res[0]
        assert res[0].info['bar'] == text, res[0]


    def test_handle_info_json_multiple_keys_404(self):
        """Test handle info in JSON with multiple keys not found works."""
        TaskFactory.create(info={'foo': 'bar', 'daniel': 'foo'})
        info = 'foo::bar|bar::foo'
        res = self.task_repo.filter_tasks_by(info=info)
        assert len(res) == 0

    def test_handle_info_json_multiple_keys_404_with_one_pipe(self):
        """Test handle info in JSON with multiple keys not found works."""
        TaskFactory.create(info={'foo': 'bar', 'bar': 'foo'})
        info = 'foo::bar|'
        res = self.task_repo.filter_tasks_by(info=info)
        assert len(res) == 1
        assert res[0].info['foo'] == 'bar', res[0]
        assert res[0].info['bar'] == 'foo', res[0]

    def test_handle_info_json_multiple_keys_404_fulltextsearch(self):
        """Test handle info in JSON with full text
        search with multiple keys not found works."""
        TaskFactory.create(info={'foo': 'bar', 'bar': 'foo'})
        info = 'foo::bar|'
        res = self.task_repo.filter_tasks_by(info=info, fulltextsearch='1')
        assert len(res) == 1
        assert res[0].info['foo'] == 'bar', res[0]
        assert res[0].info['bar'] == 'foo', res[0]


    def test_handle_info_json_wrong_data(self):
        """Test handle info in JSON with wrong data works."""
        TaskFactory.create(info={'foo': 'bar', 'bar': 'foo'})

        infos = ['|', '||', '|::', ':|', '::|', '|:', 'foo|', 'foo|']
        for info in infos:
            res = self.task_repo.filter_tasks_by(info=info)
            assert len(res) == 0

        for info in infos:
            res = self.task_repo.filter_tasks_by(info=info, fulltextsearch='1')
            assert len(res) == 0

    def test_get_task_return_none_if_no_task(self):
        """Test get_task method returns None if there is no task with the
        specified id"""

        task = self.task_repo.get_task(200)

        assert task is None, task


    def test_get_task_returns_task(self):
        """Test get_task method returns a task if exists"""

        task = TaskFactory.create()

        retrieved_task = self.task_repo.get_task(task.id)

        assert task == retrieved_task, retrieved_task


    def test_get_task_by(self):
        """Test get_task_by returns a task with the specified attribute"""

        task = TaskFactory.create(state='done')

        retrieved_task = self.task_repo.get_task_by(state=task.state)

        assert task == retrieved_task, retrieved_task


    def test_get_task_by_returns_none_if_no_task(self):
        """Test get_task_by returns None if no task matches the query"""

        TaskFactory.create(state='done')

        task = self.task_repo.get_task_by(state='ongoing')

        assert task is None, task


    def test_filter_tasks_by_no_matches(self):
        """Test filter_tasks_by returns an empty list if no tasks match the query"""

        TaskFactory.create(state='done', n_answers=17)

        retrieved_tasks = self.task_repo.filter_tasks_by(state='ongoing')

        assert isinstance(retrieved_tasks, list)
        assert len(retrieved_tasks) == 0, retrieved_tasks


    def test_filter_tasks_by_one_condition(self):
        """Test filter_tasks_by returns a list of tasks that meet the filtering
        condition"""

        TaskFactory.create_batch(3, state='done')
        should_be_missing = TaskFactory.create(state='ongoing')

        retrieved_tasks = self.task_repo.filter_tasks_by(state='done')

        assert len(retrieved_tasks) == 3, retrieved_tasks
        assert should_be_missing not in retrieved_tasks, retrieved_tasks


    def test_filter_tasks_by_multiple_conditions(self):
        """Test filter_tasks_by supports multiple-condition queries"""

        TaskFactory.create(state='done', n_answers=17)
        task = TaskFactory.create(state='done', n_answers=99)

        retrieved_tasks = self.task_repo.filter_tasks_by(state='done',
                                                         n_answers=99)

        assert len(retrieved_tasks) == 1, retrieved_tasks
        assert task in retrieved_tasks, retrieved_tasks


    def test_filter_tasks_support_yield_option(self):
        """Test that filter_tasks_by with the yielded=True option returns the
        results as a generator"""

        tasks = TaskFactory.create_batch(2, state='done')

        yielded_tasks = self.task_repo.filter_tasks_by(state='done', yielded=True)

        import types
        assert isinstance(yielded_tasks.__iter__(), types.GeneratorType)
        for task in yielded_tasks:
            assert task in tasks


    def test_filter_tasks_limit_offset(self):
        """Test that filter_tasks_by supports limit and offset options"""

        TaskFactory.create_batch(4)
        all_tasks = self.task_repo.filter_tasks_by()

        first_two = self.task_repo.filter_tasks_by(limit=2)
        last_two = self.task_repo.filter_tasks_by(limit=2, offset=2)

        assert len(first_two) == 2, first_two
        assert len(last_two) == 2, last_two
        assert first_two == all_tasks[:2]
        assert last_two == all_tasks[2:]


    def test_count_tasks_with_no_matches(self):
        """Test count_tasks_with returns 0 if no tasks match the query"""

        TaskFactory.create(state='done', n_answers=17)

        count = self.task_repo.count_tasks_with(state='ongoing')

        assert count == 0, count


    def test_count_tasks_with_one_condition(self):
        """Test count_tasks_with returns the number of tasks that meet the
        filtering condition"""

        TaskFactory.create_batch(3, state='done')
        should_be_missing = TaskFactory.create(state='ongoing')

        count = self.task_repo.count_tasks_with(state='done')

        assert count == 3, count


    def test_count_tasks_with_multiple_conditions(self):
        """Test count_tasks_with supports multiple-condition queries"""

        TaskFactory.create(state='done', n_answers=17)
        task = TaskFactory.create(state='done', n_answers=99)

        count = self.task_repo.count_tasks_with(state='done', n_answers=99)

        assert count == 1, count
class TestTaskRepositorySaveDeleteUpdate(Test):

    def setUp(self):
        super(TestTaskRepositorySaveDeleteUpdate, self).setUp()
        self.task_repo = TaskRepository(db)


    def test_save_saves_tasks(self):
        """Test save persists Task instances"""

        task = TaskFactory.build()
        assert self.task_repo.get_task(task.id) is None

        self.task_repo.save(task)

        assert self.task_repo.get_task(task.id) == task, "Task not saved"


    def test_save_saves_taskruns(self):
        """Test save persists TaskRun instances"""

        taskrun = TaskRunFactory.build()
        assert self.task_repo.get_task_run(taskrun.id) is None

        self.task_repo.save(taskrun)

        assert self.task_repo.get_task_run(taskrun.id) == taskrun, "TaskRun not saved"


    def test_save_fails_if_integrity_error(self):
        """Test save raises a DBIntegrityError if the instance to be saved lacks
        a required value"""

        task = TaskFactory.build(project_id=None, project=None)

        assert_raises(DBIntegrityError, self.task_repo.save, task)


    def test_save_only_saves_tasks_and_taskruns(self):
        """Test save raises a WrongObjectError when an object which is neither
        a Task nor a Taskrun instance is saved"""

        bad_object = dict()

        assert_raises(WrongObjectError, self.task_repo.save, bad_object)


    def test_update_task(self):
        """Test update persists the changes made to Task instances"""

        task = TaskFactory.create(state='ongoing')
        task.state = 'done'

        self.task_repo.update(task)
        updated_task = self.task_repo.get_task(task.id)

        assert updated_task.state == 'done', updated_task


    def test_update_taskrun(self):
        """Test update persists the changes made to TaskRun instances"""

        taskrun = TaskRunFactory.create(info='info')
        taskrun.info = 'updated info!'

        self.task_repo.update(taskrun)
        updated_taskrun = self.task_repo.get_task_run(taskrun.id)

        assert updated_taskrun.info == 'updated info!', updated_taskrun


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

        task = TaskFactory.create()
        task.project_id = None

        assert_raises(DBIntegrityError, self.task_repo.update, task)


    def test_update_only_updates_tasks_and_taskruns(self):
        """Test update raises a WrongObjectError when an object which is neither
        a Task nor a TaskRun instance is updated"""

        bad_object = dict()

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


    def test_delete_task(self):
        """Test delete removes the Task instance"""

        task = TaskFactory.create()

        self.task_repo.delete(task)
        deleted = self.task_repo.get_task(task.id)

        assert deleted is None, deleted


    def test_delete_task_deletes_dependent_taskruns(self):
        """Test delete removes the dependent TaskRun instances"""

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

        self.task_repo.delete(task)
        deleted = self.task_repo.get_task_run(taskrun.id)

        assert deleted is None, deleted


    def test_delete_taskrun(self):
        """Test delete removes the TaskRun instance"""

        taskrun = TaskRunFactory.create()

        self.task_repo.delete(taskrun)
        deleted = self.task_repo.get_task_run(taskrun.id)

        assert deleted is None, deleted


    def test_delete_only_deletes_tasks(self):
        """Test delete raises a WrongObjectError if is requested to delete other
        than a task"""

        bad_object = dict()

        assert_raises(WrongObjectError, self.task_repo.delete, bad_object)


    def test_delete_valid_from_project_deletes_many_tasks(self):
        """Test delete_valid_from_project deletes many tasks at once"""

        tasks = TaskFactory.create_batch(2)

        project = project_repo.get(tasks[0].project_id)

        self.task_repo.delete_valid_from_project(project)

        tasks = self.task_repo.filter_tasks_by(project_id=project.id)

        assert len(tasks) == 0, len(tasks)


    def test_delete_valid_from_project_deletes_dependent(self):
        """Test delete_valid_from_project deletes dependent taskruns too"""

        task = TaskFactory.create()
        taskrun = TaskRunFactory.create(task=task)
        task_run_id = taskrun.id
        project = project_repo.get(task.project_id)

        self.task_repo.delete_valid_from_project(project)
        deleted = self.task_repo.get_task_run(id=task_run_id)

        assert deleted is None, deleted


    def test_delete_valid_from_project_deletes_dependent_without_result(self):
        """Test delete_valid_from_project deletes dependent taskruns without result"""

        task = TaskFactory.create(n_answers=1)
        project = project_repo.get(task.project_id)
        taskrun = TaskRunFactory.create(task=task)
        task2 = TaskFactory.create(project=project)
        TaskRunFactory.create(task=task2)

        self.task_repo.delete_valid_from_project(project)
        non_deleted = self.task_repo.filter_tasks_by(project_id=project.id)

        err_msg = "There should be one task, as it belongs to a result"
        assert len(non_deleted) == 1, err_msg
        assert non_deleted[0].id == task.id, err_msg

        non_deleted = self.task_repo.filter_task_runs_by(project_id=project.id)

        err_msg = "There should be one task_run, as it belongs to a result"
        assert len(non_deleted) == 1, err_msg
        assert non_deleted[0].id == taskrun.id, err_msg


    def test_delete_taskruns_from_project_deletes_taskruns(self):
        task = TaskFactory.create()
        project = project_repo.get(task.project_id)
        taskrun = TaskRunFactory.create(task=task)

        self.task_repo.delete_taskruns_from_project(project)
        taskruns = self.task_repo.filter_task_runs_by(project_id=project.id)

        assert taskruns == [], taskruns


    def test_update_tasks_redundancy_changes_all_project_tasks_redundancy(self):
        """Test update_tasks_redundancy updates the n_answers value for every
        task in the project"""

        project = ProjectFactory.create()
        TaskFactory.create_batch(2, project=project, n_answers=1)

        self.task_repo.update_tasks_redundancy(project, 2)
        tasks = self.task_repo.filter_tasks_by(project_id=project.id)

        for task in tasks:
            assert task.n_answers == 2, task.n_answers


    def test_update_tasks_redundancy_updates_state_when_incrementing(self):
        """Test update_tasks_redundancy changes 'completed' tasks to 'ongoing'
        if n_answers is incremented enough"""

        project = ProjectFactory.create()
        tasks = TaskFactory.create_batch(2, project=project, n_answers=2)
        TaskRunFactory.create_batch(2, task=tasks[0])
        tasks[0].state = 'completed'
        self.task_repo.update(tasks[0])

        assert tasks[0].state == 'completed', tasks[0].state
        assert tasks[1].state == 'ongoing', tasks[1].state

        self.task_repo.update_tasks_redundancy(project, 3)
        tasks = self.task_repo.filter_tasks_by(project_id=project.id)

        for task in tasks:
            assert task.state == 'ongoing', task.state


    def test_update_tasks_redundancy_updates_state_when_decrementing(self):
        """Test update_tasks_redundancy changes 'ongoing' tasks to 'completed'
        if n_answers is decremented enough"""

        project = ProjectFactory.create()
        tasks = TaskFactory.create_batch(2, project=project, n_answers=2)
        TaskRunFactory.create_batch(2, task=tasks[0])
        TaskRunFactory.create(task=tasks[1])
        tasks[0].state = 'completed'
        self.task_repo.update(tasks[0])

        assert tasks[0].state == 'completed', tasks[0].state
        assert tasks[1].state == 'ongoing', tasks[1].state

        self.task_repo.update_tasks_redundancy(project, 1)
        tasks = self.task_repo.filter_tasks_by(project_id=project.id)

        for task in tasks:
            assert task.state == 'completed', task.state
Beispiel #6
0
class TestTaskRepositorySaveDeleteUpdate(Test):

    def setUp(self):
        super(TestTaskRepositorySaveDeleteUpdate, self).setUp()
        self.task_repo = TaskRepository(db)


    @with_context
    def test_save_saves_tasks(self):
        """Test save persists Task instances"""

        task = TaskFactory.build()
        assert self.task_repo.get_task(task.id) is None

        self.task_repo.save(task)

        assert self.task_repo.get_task(task.id) == task, "Task not saved"


    @with_context
    def test_save_saves_taskruns(self):
        """Test save persists TaskRun instances"""

        taskrun = TaskRunFactory.build()
        assert self.task_repo.get_task_run(taskrun.id) is None

        self.task_repo.save(taskrun)

        assert self.task_repo.get_task_run(taskrun.id) == taskrun, "TaskRun not saved"


    @with_context
    def test_save_fails_if_integrity_error(self):
        """Test save raises a DBIntegrityError if the instance to be saved lacks
        a required value"""

        task = TaskFactory.build(project_id=None, project=None)

        assert_raises(DBIntegrityError, self.task_repo.save, task)


    @with_context
    def test_save_only_saves_tasks_and_taskruns(self):
        """Test save raises a WrongObjectError when an object which is neither
        a Task nor a Taskrun instance is saved"""

        bad_object = dict()

        assert_raises(WrongObjectError, self.task_repo.save, bad_object)


    @with_context
    def test_update_task(self):
        """Test update persists the changes made to Task instances"""

        task = TaskFactory.create(state='ongoing')
        task.state = 'done'

        self.task_repo.update(task)
        updated_task = self.task_repo.get_task(task.id)

        assert updated_task.state == 'done', updated_task


    @with_context
    def test_update_taskrun(self):
        """Test update persists the changes made to TaskRun instances"""

        taskrun = TaskRunFactory.create(info='info')
        taskrun.info = 'updated info!'

        self.task_repo.update(taskrun)
        updated_taskrun = self.task_repo.get_task_run(taskrun.id)

        assert updated_taskrun.info == 'updated info!', updated_taskrun


    @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"""

        task = TaskFactory.create()
        task.project_id = None

        assert_raises(DBIntegrityError, self.task_repo.update, task)


    @with_context
    def test_update_only_updates_tasks_and_taskruns(self):
        """Test update raises a WrongObjectError when an object which is neither
        a Task nor a TaskRun instance is updated"""

        bad_object = dict()

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


    @with_context
    def test_delete_task(self):
        """Test delete removes the Task instance"""

        task = TaskFactory.create()

        self.task_repo.delete(task)
        deleted = self.task_repo.get_task(task.id)

        assert deleted is None, deleted


    @with_context
    def test_delete_task_deletes_dependent_taskruns(self):
        """Test delete removes the dependent TaskRun instances"""

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

        self.task_repo.delete(task)
        deleted = self.task_repo.get_task_run(taskrun.id)

        assert deleted is None, deleted


    @with_context
    def test_delete_taskrun(self):
        """Test delete removes the TaskRun instance"""

        taskrun = TaskRunFactory.create()

        self.task_repo.delete(taskrun)
        deleted = self.task_repo.get_task_run(taskrun.id)

        assert deleted is None, deleted


    @with_context
    def test_delete_only_deletes_tasks(self):
        """Test delete raises a WrongObjectError if is requested to delete other
        than a task"""

        bad_object = dict()

        assert_raises(WrongObjectError, self.task_repo.delete, bad_object)


    @with_context
    def test_delete_valid_from_project_deletes_many_tasks(self):
        """Test delete_valid_from_project deletes many tasks at once"""

        tasks = TaskFactory.create_batch(2)

        project = project_repo.get(tasks[0].project_id)

        self.task_repo.delete_valid_from_project(project)

        tasks = self.task_repo.filter_tasks_by(project_id=project.id)

        assert len(tasks) == 0, len(tasks)


    @with_context
    def test_delete_valid_from_project_deletes_dependent(self):
        """Test delete_valid_from_project deletes dependent taskruns too"""

        task = TaskFactory.create()
        taskrun = TaskRunFactory.create(task=task)
        task_run_id = taskrun.id
        project = project_repo.get(task.project_id)

        self.task_repo.delete_valid_from_project(project)
        deleted = self.task_repo.get_task_run(id=task_run_id)

        assert deleted is None, deleted


    @with_context
    def test_delete_valid_from_project_deletes_dependent_without_result(self):
        """Test delete_valid_from_project deletes dependent taskruns without result"""

        task = TaskFactory.create(n_answers=1)
        project = project_repo.get(task.project_id)
        taskrun = TaskRunFactory.create(task=task)
        task2 = TaskFactory.create(project=project)
        TaskRunFactory.create(task=task2)

        self.task_repo.delete_valid_from_project(project)
        non_deleted = self.task_repo.filter_tasks_by(project_id=project.id)

        err_msg = "There should be one task, as it belongs to a result"
        assert len(non_deleted) == 1, err_msg
        assert non_deleted[0].id == task.id, err_msg

        non_deleted = self.task_repo.filter_task_runs_by(project_id=project.id)

        err_msg = "There should be one task_run, as it belongs to a result"
        assert len(non_deleted) == 1, err_msg
        assert non_deleted[0].id == taskrun.id, err_msg


    @with_context
    def test_delete_taskruns_from_project_deletes_taskruns(self):
        task = TaskFactory.create()
        project = project_repo.get(task.project_id)
        taskrun = TaskRunFactory.create(task=task)

        self.task_repo.delete_taskruns_from_project(project)
        taskruns = self.task_repo.filter_task_runs_by(project_id=project.id)

        assert taskruns == [], taskruns


    @with_context
    def test_update_tasks_redundancy_changes_all_project_tasks_redundancy(self):
        """Test update_tasks_redundancy updates the n_answers value for every
        task in the project"""

        project = ProjectFactory.create()
        TaskFactory.create_batch(2, project=project, n_answers=1)

        self.task_repo.update_tasks_redundancy(project, 2)
        tasks = self.task_repo.filter_tasks_by(project_id=project.id)

        for task in tasks:
            assert task.n_answers == 2, task.n_answers


    @with_context
    def test_update_tasks_redundancy_updates_state_when_incrementing(self):
        """Test update_tasks_redundancy changes 'completed' tasks to 'ongoing'
        if n_answers is incremented enough"""

        project = ProjectFactory.create()
        tasks = TaskFactory.create_batch(2, project=project, n_answers=2)
        TaskRunFactory.create_batch(2, task=tasks[0])
        tasks[0].state = 'completed'
        self.task_repo.update(tasks[0])

        assert tasks[0].state == 'completed', tasks[0].state
        assert tasks[1].state == 'ongoing', tasks[1].state

        self.task_repo.update_tasks_redundancy(project, 3)
        tasks = self.task_repo.filter_tasks_by(project_id=project.id)

        for task in tasks:
            assert task.state == 'ongoing', task.state


    @with_context
    def test_update_tasks_redundancy_updates_state_when_decrementing(self):
        """Test update_tasks_redundancy changes 'ongoing' tasks to 'completed'
        if n_answers is decremented enough"""

        project = ProjectFactory.create()
        tasks = TaskFactory.create_batch(2, project=project, n_answers=2)
        TaskRunFactory.create_batch(2, task=tasks[0])
        TaskRunFactory.create(task=tasks[1])
        tasks[0].state = 'completed'
        self.task_repo.update(tasks[0])

        assert tasks[0].state == 'completed', tasks[0].state
        assert tasks[1].state == 'ongoing', tasks[1].state

        self.task_repo.update_tasks_redundancy(project, 1)
        tasks = self.task_repo.filter_tasks_by(project_id=project.id)

        for task in tasks:
            assert task.state == 'completed', task.state
Beispiel #7
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
        })
class TestTaskRepositoryForTaskQueries(Test):

    def setUp(self):
        super(TestTaskRepositoryForTaskQueries, self).setUp()
        self.task_repo = TaskRepository(db)


    def test_get_task_return_none_if_no_task(self):
        """Test get_task method returns None if there is no task with the
        specified id"""

        task = self.task_repo.get_task(200)

        assert task is None, task


    def test_get_task_returns_task(self):
        """Test get_task method returns a task if exists"""

        task = TaskFactory.create()

        retrieved_task = self.task_repo.get_task(task.id)

        assert task == retrieved_task, retrieved_task


    def test_get_task_by(self):
        """Test get_task_by returns a task with the specified attribute"""

        task = TaskFactory.create(state='done')

        retrieved_task = self.task_repo.get_task_by(state=task.state)

        assert task == retrieved_task, retrieved_task


    def test_get_task_by_returns_none_if_no_task(self):
        """Test get_task_by returns None if no task matches the query"""

        TaskFactory.create(state='done')

        task = self.task_repo.get_task_by(state='ongoing')

        assert task is None, task


    def test_filter_tasks_by_no_matches(self):
        """Test filter_tasks_by returns an empty list if no tasks match the query"""

        TaskFactory.create(state='done', n_answers=17)

        retrieved_tasks = self.task_repo.filter_tasks_by(state='ongoing')

        assert isinstance(retrieved_tasks, list)
        assert len(retrieved_tasks) == 0, retrieved_tasks


    def test_filter_tasks_by_one_condition(self):
        """Test filter_tasks_by returns a list of tasks that meet the filtering
        condition"""

        TaskFactory.create_batch(3, state='done')
        should_be_missing = TaskFactory.create(state='ongoing')

        retrieved_tasks = self.task_repo.filter_tasks_by(state='done')

        assert len(retrieved_tasks) == 3, retrieved_tasks
        assert should_be_missing not in retrieved_tasks, retrieved_tasks


    def test_filter_tasks_by_multiple_conditions(self):
        """Test filter_tasks_by supports multiple-condition queries"""

        TaskFactory.create(state='done', n_answers=17)
        task = TaskFactory.create(state='done', n_answers=99)

        retrieved_tasks = self.task_repo.filter_tasks_by(state='done',
                                                         n_answers=99)

        assert len(retrieved_tasks) == 1, retrieved_tasks
        assert task in retrieved_tasks, retrieved_tasks


    def test_filter_tasks_support_yield_option(self):
        """Test that filter_tasks_by with the yielded=True option returns the
        results as a generator"""

        tasks = TaskFactory.create_batch(2, state='done')

        yielded_tasks = self.task_repo.filter_tasks_by(state='done', yielded=True)

        import types
        assert isinstance(yielded_tasks.__iter__(), types.GeneratorType)
        for task in yielded_tasks:
            assert task in tasks


    def test_filter_tasks_limit_offset(self):
        """Test that filter_tasks_by supports limit and offset options"""

        TaskFactory.create_batch(4)
        all_tasks = self.task_repo.filter_tasks_by()

        first_two = self.task_repo.filter_tasks_by(limit=2)
        last_two = self.task_repo.filter_tasks_by(limit=2, offset=2)

        assert len(first_two) == 2, first_two
        assert len(last_two) == 2, last_two
        assert first_two == all_tasks[:2]
        assert last_two == all_tasks[2:]


    def test_count_tasks_with_no_matches(self):
        """Test count_tasks_with returns 0 if no tasks match the query"""

        TaskFactory.create(state='done', n_answers=17)

        count = self.task_repo.count_tasks_with(state='ongoing')

        assert count == 0, count


    def test_count_tasks_with_one_condition(self):
        """Test count_tasks_with returns the number of tasks that meet the
        filtering condition"""

        TaskFactory.create_batch(3, state='done')
        should_be_missing = TaskFactory.create(state='ongoing')

        count = self.task_repo.count_tasks_with(state='done')

        assert count == 3, count


    def test_count_tasks_with_multiple_conditions(self):
        """Test count_tasks_with supports multiple-condition queries"""

        TaskFactory.create(state='done', n_answers=17)
        task = TaskFactory.create(state='done', n_answers=99)

        count = self.task_repo.count_tasks_with(state='done', n_answers=99)

        assert count == 1, count
Beispiel #9
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
        })
class TestTaskRepositorySaveDeleteUpdate(Test):

    def setUp(self):
        super(TestTaskRepositorySaveDeleteUpdate, self).setUp()
        self.task_repo = TaskRepository(db)


    def test_save_saves_tasks(self):
        """Test save persists Task instances"""

        task = TaskFactory.build()
        assert self.task_repo.get_task(task.id) is None

        self.task_repo.save(task)

        assert self.task_repo.get_task(task.id) == task, "Task not saved"


    def test_save_saves_taskruns(self):
        """Test save persists TaskRun instances"""

        taskrun = TaskRunFactory.build()
        assert self.task_repo.get_task_run(taskrun.id) is None

        self.task_repo.save(taskrun)

        assert self.task_repo.get_task_run(taskrun.id) == taskrun, "TaskRun not saved"


    def test_save_fails_if_integrity_error(self):
        """Test save raises a DBIntegrityError if the instance to be saved lacks
        a required value"""

        task = TaskFactory.build(app_id=None, app=None)

        assert_raises(DBIntegrityError, self.task_repo.save, task)


    def test_save_only_saves_tasks_and_taskruns(self):
        """Test save raises a WrongObjectError when an object which is neither
        a Task nor a Taskrun instance is saved"""

        bad_object = dict()

        assert_raises(WrongObjectError, self.task_repo.save, bad_object)


    def test_update_task(self):
        """Test update persists the changes made to Task instances"""

        task = TaskFactory.create(state='ongoing')
        task.state = 'done'

        self.task_repo.update(task)
        updated_task = self.task_repo.get_task(task.id)

        assert updated_task.state == 'done', updated_task


    def test_update_taskrun(self):
        """Test update persists the changes made to TaskRun instances"""

        taskrun = TaskRunFactory.create(info='info')
        taskrun.info = 'updated info!'

        self.task_repo.update(taskrun)
        updated_taskrun = self.task_repo.get_task_run(taskrun.id)

        assert updated_taskrun.info == 'updated info!', updated_taskrun


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

        task = TaskFactory.create()
        task.app_id = None

        assert_raises(DBIntegrityError, self.task_repo.update, task)


    def test_update_only_updates_tasks_and_taskruns(self):
        """Test update raises a WrongObjectError when an object which is neither
        a Task nor a TaskRun instance is updated"""

        bad_object = dict()

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


    def test_delete_task(self):
        """Test delete removes the Task instance"""

        task = TaskFactory.create()

        self.task_repo.delete(task)
        deleted = self.task_repo.get_task(task.id)

        assert deleted is None, deleted


    def test_delete_task_deletes_dependent_taskruns(self):
        """Test delete removes the dependent TaskRun instances"""

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

        self.task_repo.delete(task)
        deleted = self.task_repo.get_task_run(taskrun.id)

        assert deleted is None, deleted


    def test_delete_taskrun(self):
        """Test delete removes the TaskRun instance"""

        taskrun = TaskRunFactory.create()

        self.task_repo.delete(taskrun)
        deleted = self.task_repo.get_task_run(taskrun.id)

        assert deleted is None, deleted


    def test_delete_only_deletes_tasks(self):
        """Test delete raises a WrongObjectError if is requested to delete other
        than a task"""

        bad_object = dict()

        assert_raises(WrongObjectError, self.task_repo.delete, bad_object)


    def test_delete_all_deletes_many_tasks(self):
        """Test delete_all deletes many tasks at once"""

        tasks = TaskFactory.create_batch(2)

        self.task_repo.delete_all(tasks)

        for task in tasks:
            assert self.task_repo.get_task(task.id) is None, task


    def test_delete_all_deletes_dependent(self):
        """Test delete_all deletes dependent taskruns too"""

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

        self.task_repo.delete_all([task])
        deleted = self.task_repo.get_task_run(taskrun.id)

        assert deleted is None, deleted


    def test_delete_all_deletes_many_taskruns(self):
        """Test delete_all deletes many taskruns at once"""

        taskruns = TaskRunFactory.create_batch(2)

        self.task_repo.delete_all(taskruns)

        for taskrun in taskruns:
            assert self.task_repo.get_task_run(taskrun.id) is None, taskrun


    def test_delete_all_raises_error_if_no_task(self):
        """Test delete_all raises a WrongObjectError if is requested to delete
        any other object than a task"""

        bad_objects = [dict(), 'string']

        assert_raises(WrongObjectError, self.task_repo.delete_all, bad_objects)


    def test_update_tasks_redundancy_changes_all_project_tasks_redundancy(self):
        """Test update_tasks_redundancy updates the n_answers value for every
        task in the project"""

        project = AppFactory.create()
        TaskFactory.create_batch(2, app=project, n_answers=1)

        self.task_repo.update_tasks_redundancy(project, 2)
        tasks = self.task_repo.filter_tasks_by(app_id=project.id)

        for task in tasks:
            assert task.n_answers == 2, task.n_answers


    def test_update_tasks_redundancy_updates_state_when_incrementing(self):
        """Test update_tasks_redundancy changes 'completed' tasks to 'ongoing'
        if n_answers is incremented enough"""

        project = AppFactory.create()
        tasks = TaskFactory.create_batch(2, app=project, n_answers=2)
        TaskRunFactory.create_batch(2, task=tasks[0])
        tasks[0].state = 'completed'
        self.task_repo.update(tasks[0])

        assert tasks[0].state == 'completed', tasks[0].state
        assert tasks[1].state == 'ongoing', tasks[1].state

        self.task_repo.update_tasks_redundancy(project, 3)
        tasks = self.task_repo.filter_tasks_by(app_id=project.id)

        for task in tasks:
            assert task.state == 'ongoing', task.state


    def test_update_tasks_redundancy_updates_state_when_decrementing(self):
        """Test update_tasks_redundancy changes 'ongoing' tasks to 'completed'
        if n_answers is decremented enough"""

        project = AppFactory.create()
        tasks = TaskFactory.create_batch(2, app=project, n_answers=2)
        TaskRunFactory.create_batch(2, task=tasks[0])
        TaskRunFactory.create(task=tasks[1])
        tasks[0].state = 'completed'
        self.task_repo.update(tasks[0])

        assert tasks[0].state == 'completed', tasks[0].state
        assert tasks[1].state == 'ongoing', tasks[1].state

        self.task_repo.update_tasks_redundancy(project, 1)
        tasks = self.task_repo.filter_tasks_by(app_id=project.id)

        for task in tasks:
            assert task.state == 'completed', task.state