예제 #1
0
class TestProjectRepositoryForCategories(Test):

    def setUp(self):
        super(TestProjectRepositoryForCategories, self).setUp()
        self.project_repo = ProjectRepository(db)


    def test_get_category_return_none_if_no_category(self):
        """Test get_category method returns None if there is no category with
        the specified id"""

        category = self.project_repo.get_category(200)

        assert category is None, category


    def test_get_category_returns_category(self):
        """Test get_category method returns a category if exists"""

        category = CategoryFactory.create()

        retrieved_category = self.project_repo.get_category(category.id)

        assert category == retrieved_category, retrieved_category


    def test_get_category_by(self):
        """Test get_category returns a category with the specified attribute"""

        category = CategoryFactory.create(name='My Cat', short_name='mycat')

        retrieved_category = self.project_repo.get_category_by(name=category.name)

        assert category == retrieved_category, retrieved_category


    def test_get_category_by_returns_none_if_no_category(self):
        """Test get_category returns None if no category matches the query"""

        CategoryFactory.create(name='My Project', short_name='mycategory')

        category = self.project_repo.get_by(name='no_name')

        assert category is None, category


    def get_all_returns_list_of_all_categories(self):
        """Test get_all_categories returns a list of all the existing categories"""

        categories = CategoryFactory.create_batch(3)

        retrieved_categories = self.project_repo.get_all_categories()

        assert isinstance(retrieved_categories, list)
        assert len(retrieved_categories) == len(categories), retrieved_categories
        for category in retrieved_categories:
            assert category in categories, category


    def test_filter_categories_by_no_matches(self):
        """Test filter_categories_by returns an empty list if no categories
        match the query"""

        CategoryFactory.create(name='My Project', short_name='mycategory')

        retrieved_categories = self.project_repo.filter_categories_by(name='no_name')

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


    def test_filter_categories_by_one_condition(self):
        """Test filter_categories_by returns a list of categories that meet
        the filtering condition"""

        CategoryFactory.create_batch(3, description='generic category')
        should_be_missing = CategoryFactory.create(description='other category')

        retrieved_categories = (self.project_repo
            .filter_categories_by(description='generic category'))

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


    def test_filter_categories_by_limit_offset(self):
        """Test that filter_categories_by supports limit and offset options"""

        CategoryFactory.create_batch(4)
        all_categories = self.project_repo.filter_categories_by()

        first_two = self.project_repo.filter_categories_by(limit=2)
        last_two = self.project_repo.filter_categories_by(limit=2, offset=2)

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


    def test_save_category(self):
        """Test save_category persist the category"""

        category = CategoryFactory.build()
        assert self.project_repo.get(category.id) is None

        self.project_repo.save_category(category)

        assert self.project_repo.get_category(category.id) == category, "Category not saved"


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

        category = CategoryFactory.build(name=None)

        assert_raises(DBIntegrityError, self.project_repo.save_category, category)


    def test_save_category_only_saves_categories(self):
        """Test save_category raises a WrongObjectError when an object which is
        not a Category instance is saved"""

        bad_object = ProjectFactory.build()

        assert_raises(WrongObjectError, self.project_repo.save_category, bad_object)


    def test_update_category(self):
        """Test update_category persists the changes made to the category"""

        category = CategoryFactory.create(description='this is a category')
        category.description = 'the description has changed'

        self.project_repo.update_category(category)
        updated_category = self.project_repo.get_category(category.id)

        assert updated_category.description == 'the description has changed', updated_category


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

        category = CategoryFactory.create()
        category.name = None

        assert_raises(DBIntegrityError, self.project_repo.update_category, category)


    def test_update_category_only_updates_categories(self):
        """Test update_category raises a WrongObjectError when an object which is
        not a Category instance is updated"""

        bad_object = ProjectFactory.build()

        assert_raises(WrongObjectError, self.project_repo.update_category, bad_object)


    def test_delete_category(self):
        """Test delete_category removes the category instance"""

        category = CategoryFactory.create()

        self.project_repo.delete_category(category)
        deleted = self.project_repo.get_category(category.id)

        assert deleted is None, deleted


    def test_delete_category_only_deletes_categories(self):
        """Test delete_category raises a WrongObjectError if is requested to
        delete other than a category"""

        bad_object = dict()

        assert_raises(WrongObjectError, self.project_repo.delete_category, bad_object)
예제 #2
0
class TestProjectRepositoryForCategories(Test):
    def setUp(self):
        super(TestProjectRepositoryForCategories, self).setUp()
        self.project_repo = ProjectRepository(db)

    @with_context
    def test_get_category_return_none_if_no_category(self):
        """Test get_category method returns None if there is no category with
        the specified id"""

        category = self.project_repo.get_category(200)

        assert category is None, category

    @with_context
    def test_get_category_returns_category(self):
        """Test get_category method returns a category if exists"""

        category = CategoryFactory.create()

        retrieved_category = self.project_repo.get_category(category.id)

        assert category == retrieved_category, retrieved_category

    @with_context
    def test_get_category_by(self):
        """Test get_category returns a category with the specified attribute"""

        category = CategoryFactory.create(name='My Cat', short_name='mycat')

        retrieved_category = self.project_repo.get_category_by(
            name=category.name)

        assert category == retrieved_category, retrieved_category

    @with_context
    def test_get_category_by_returns_none_if_no_category(self):
        """Test get_category returns None if no category matches the query"""

        CategoryFactory.create(name='My Project', short_name='mycategory')

        category = self.project_repo.get_by(name='no_name')

        assert category is None, category

    @with_context
    def get_all_returns_list_of_all_categories(self):
        """Test get_all_categories returns a list of all the existing categories"""

        categories = CategoryFactory.create_batch(3)

        retrieved_categories = self.project_repo.get_all_categories()

        assert isinstance(retrieved_categories, list)
        assert len(retrieved_categories) == len(
            categories), retrieved_categories
        for category in retrieved_categories:
            assert category in categories, category

    @with_context
    def test_filter_categories_by_no_matches(self):
        """Test filter_categories_by returns an empty list if no categories
        match the query"""

        CategoryFactory.create(name='My Project', short_name='mycategory')

        retrieved_categories = self.project_repo.filter_categories_by(
            name='no_name')

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

    @with_context
    def test_filter_categories_by_ownerid(self):
        """Test filter_categories_by removes ownerid from query."""

        CategoryFactory.create(name='My Project', short_name='mycategory')

        retrieved_categories = self.project_repo.filter_categories_by(
            short_name='mycategory', owner_id=1)

        assert isinstance(retrieved_categories, list)
        assert len(retrieved_categories) == 1, retrieved_categories

    @with_context
    def test_filter_categories_by_one_condition(self):
        """Test filter_categories_by returns a list of categories that meet
        the filtering condition"""

        CategoryFactory.create_batch(3, description='generic category')
        should_be_missing = CategoryFactory.create(
            description='other category')

        retrieved_categories = (self.project_repo.filter_categories_by(
            description='generic category'))

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

    @with_context
    def test_filter_categories_by_limit_offset(self):
        """Test that filter_categories_by supports limit and offset options"""

        CategoryFactory.create_batch(4)
        all_categories = self.project_repo.filter_categories_by()

        first_two = self.project_repo.filter_categories_by(limit=2)
        last_two = self.project_repo.filter_categories_by(limit=2, offset=2)

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

    @with_context
    def test_save_category(self):
        """Test save_category persist the category"""

        category = CategoryFactory.build()
        assert self.project_repo.get(category.id) is None

        self.project_repo.save_category(category)

        assert self.project_repo.get_category(
            category.id) == category, "Category not saved"

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

        category = CategoryFactory.build(name=None)

        assert_raises(DBIntegrityError, self.project_repo.save_category,
                      category)

    @with_context
    def test_save_category_only_saves_categories(self):
        """Test save_category raises a WrongObjectError when an object which is
        not a Category instance is saved"""

        bad_object = ProjectFactory.build()

        assert_raises(WrongObjectError, self.project_repo.save_category,
                      bad_object)

    @with_context
    def test_update_category(self):
        """Test update_category persists the changes made to the category"""

        category = CategoryFactory.create(description='this is a category')
        category.description = 'the description has changed'

        self.project_repo.update_category(category)
        updated_category = self.project_repo.get_category(category.id)

        assert updated_category.description == 'the description has changed', updated_category

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

        category = CategoryFactory.create()
        category.name = None

        assert_raises(DBIntegrityError, self.project_repo.update_category,
                      category)

    @with_context
    def test_update_category_only_updates_categories(self):
        """Test update_category raises a WrongObjectError when an object which is
        not a Category instance is updated"""

        bad_object = ProjectFactory.build()

        assert_raises(WrongObjectError, self.project_repo.update_category,
                      bad_object)

    @with_context
    def test_delete_category(self):
        """Test delete_category removes the category instance"""

        category = CategoryFactory.create()

        self.project_repo.delete_category(category)
        deleted = self.project_repo.get_category(category.id)

        assert deleted is None, deleted

    @with_context
    def test_delete_category_only_deletes_categories(self):
        """Test delete_category raises a WrongObjectError if is requested to
        delete other than a category"""

        bad_object = dict()

        assert_raises(WrongObjectError, self.project_repo.delete_category,
                      bad_object)
예제 #3
0
class TestBaseAnalyst(Test):
    def setUp(self):
        super(TestBaseAnalyst, self).setUp()
        BaseAnalyst.__abstractmethods__ = frozenset()
        self.ctx = ContextFixtures()
        self.base_analyst = BaseAnalyst()
        self.project_repo = ProjectRepository(db)
        self.result_repo = ResultRepository(db)
        self.task_repo = TaskRepository(db)
        assert_dict_equal.__self__.maxDiff = None
        assert_equal.__self__.maxDiff = None

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    @with_context
    def test_analysis_exception_if_no_annotation_collection(self):
        """Test that AnnotationCollection must be setup."""
        task = self.ctx.create_task(1, 'example.com', anno_collection=None)
        TaskRunFactory.create(task=task)
        result = self.result_repo.filter_by(project_id=task.project_id)[0]
        assert_raises(AnalysisException, self.base_analyst.analyse, result.id)