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