예제 #1
0
    def test_search(self):
        instance = self._create_test_data()
        search.index_entity(instance, index='test_index')

        result = search.search('test_index', '')

        assert not result.error
        assert len(result.items) == 1

        results, error, next_page_token = search.to_entities(result)
        assert len(results) == 1

        # with sorting
        result = search.search('test_index', '', sort='string')

        assert not result.error
        assert len(result.items) == 1

        result = search.search('test_index', '', sort='-string')

        assert not result.error
        assert len(result.items) == 1

        result = search.search('test_index',
                               '',
                               sort=search_api.SortExpression('string'))

        assert not result.error
        assert len(result.items) == 1

        # test error handling
        with mock.patch('ferris3.search.search_api.Index.search',
                        side_effect=search_api.Error(":(")):
            result = search.search('test_index', '', sort='-string')
            assert result.error
예제 #2
0
 def put_async(*args, **kwargs):
     callers = [i[3] for i in inspect.stack()]
     self.assertIn('schedule_request', callers)
     out = ndb.Future()
     if (index[0] % SKIP) == 2:
         out.set_exception(search.Error())
     else:
         out.set_result(old_put_async(*args, **kwargs).get_result())
     return out
예제 #3
0
class BaseModelTest(loanertest.TestCase, parameterized.TestCase):
    """Tests for BaseModel class."""
    @mock.patch.object(base_model, 'taskqueue')
    def test_stream_to_bq(self, mock_taskqueue):
        test_shelf = shelf_model.Shelf(location='Here', capacity=16)
        test_shelf.put()

        test_shelf.stream_to_bq('test@{}'.format(loanertest.USER_DOMAIN),
                                'Test stream')

        self.assertTrue(mock_taskqueue.add.called)

    def test_to_json_dict(self):
        entity = TestEntity(test_string='Hello', test_geopt=ndb.GeoPt(50, 100))
        entity.test_structuredprop = TestSubEntity(test_substring='foo')
        entity.put()
        entity.test_keyproperty = entity.key
        entity.put()

        entity_dict = entity.to_json_dict()

        self.assertIsInstance(entity_dict.get('test_datetime'), int)
        self.assertIsInstance(
            entity_dict['test_structuredprop']['test_subdatetime'], int)
        self.assertIsInstance(entity_dict['test_repeatedprop'], list)

    @mock.patch.object(Test, 'to_document')
    def test_index_entities_for_search(self, mock_to_document):
        base_model.search.MAXIMUM_DOCUMENTS_PER_PUT_REQUEST = 1
        test_entity_1_key = Test(text_field='item_1').put()
        test_entity_2_key = Test(text_field='item_2').put()
        documents = [
            search.Document(
                doc_id=test_entity_1_key.urlsafe(),
                fields=[search.TextField(name='text_field', value='item_1')]),
            search.Document(
                doc_id=test_entity_2_key.urlsafe(),
                fields=[search.TextField(name='text_field', value='item_2')]),
        ]
        mock_to_document.side_effect = documents
        Test.index_entities_for_search()
        self.assertEqual(mock_to_document.call_count, 2)

    @mock.patch.object(base_model, 'logging', autospec=True)
    @mock.patch.object(base_model.BaseModel, 'to_document', autospec=True)
    def test_index_entities_for_search_error(self, mock_to_document,
                                             mock_logging):
        entity = Test(text_field='item_1').put()
        mock_to_document.side_effect = base_model.DocumentCreationError
        Test.index_entities_for_search()
        mock_logging.error.assert_called_once_with(
            base_model._CREATE_DOC_ERR_MSG, entity)

    def test_get_index(self):
        base_model.BaseModel._INDEX_NAME = None
        with self.assertRaises(ValueError):
            base_model.BaseModel.get_index()

    def test_add_docs_to_index(self):
        base_model.BaseModel._INDEX_NAME = 'test'
        test_index = base_model.BaseModel.get_index()
        base_model.BaseModel.add_docs_to_index([
            search.Document(
                doc_id='test_id',
                fields=[search.TextField(name='field_one', value='value_one')])
        ])
        self.assertIsInstance(
            test_index.get_range(start_id='test_id',
                                 limit=1,
                                 include_start_object=True)[0],
            search.Document)

    @mock.patch.object(search.Index, 'put')
    def test_add_docs_to_index_put_error(self, mock_put):
        mock_put.side_effect = [
            search.PutError(
                message='Fail!',
                results=[
                    search.PutResult(
                        code=search.OperationResult.TRANSIENT_ERROR)
                ]), None
        ]
        base_model.BaseModel.add_docs_to_index([
            search.Document(
                doc_id='test_id',
                fields=[search.TextField(name='field_one', value='value_one')])
        ])
        self.assertEqual(mock_put.call_count, 2)

    @parameterized.parameters((search.Error(), ),
                              (base_model.apiproxy_errors.OverQuotaError, ))
    @mock.patch.object(base_model, 'logging', autospec=True)
    @mock.patch.object(search.Index, 'put')
    def test_add_docs_to_index_error(self, mock_error, mock_put, mock_logging):
        mock_put.side_effect = mock_error
        base_model.BaseModel.add_docs_to_index([
            search.Document(
                doc_id='test_id',
                fields=[search.TextField(name='field_one', value='value_one')])
        ])
        self.assertEqual(mock_put.call_count, 1)
        self.assertEqual(mock_logging.error.call_count, 1)

    def test_get_doc_by_id(self):
        base_model.BaseModel._INDEX_NAME = 'test'
        test_index = base_model.BaseModel.get_index()
        test_index.put([
            search.Document(
                doc_id='test_id',
                fields=[search.TextField(name='field_one', value='value_one')])
        ])
        self.assertIsInstance(
            base_model.BaseModel.get_doc_by_id(doc_id='test_id'),
            search.Document)

    def test_remove_doc_by_id(self):
        base_model.BaseModel._INDEX_NAME = 'test'
        test_index = base_model.BaseModel.get_index()
        test_index.put([
            search.Document(
                doc_id='test_id',
                fields=[search.TextField(name='field_one', value='value_one')])
        ])
        base_model.BaseModel.remove_doc_by_id('test_id')
        test_response = test_index.get_range(start_id='test_id',
                                             limit=1,
                                             include_start_object=True)
        self.assertIsInstance(test_response, search.GetResponse)
        self.assertEqual(test_response.results, [])

    @mock.patch.object(base_model, 'logging', autospec=True)
    @mock.patch.object(search.Index, 'delete', autospec=True)
    def test_remove_doc_by_id_delete_error(self, mock_delete, mock_logging):
        mock_delete.side_effect = search.DeleteError(message='Delete Fail!',
                                                     results=[])
        base_model.BaseModel.remove_doc_by_id('test_id')
        mock_logging.error.assert_called_once_with(
            base_model._REMOVE_DOC_ERR_MSG, 'test_id')

    def test_clear_index(self):
        test_index = Test.get_index()
        test_index.put([
            search.Document(
                doc_id='test_id_one',
                fields=[search.TextField(name='field_one',
                                         value='value_one')]),
            search.Document(
                doc_id='test_id_two',
                fields=[search.AtomField(name='field_two',
                                         value='value_two')]),
            search.Document(
                doc_id='test_id_three',
                fields=[search.NumberField(name='field_three', value=3)])
        ])
        # Ensure both docs were added to the index.
        self.assertLen(test_index.get_range(ids_only=True), 3)
        Test.clear_index()
        # Ensure the index was cleared.
        self.assertFalse(test_index.get_range(ids_only=True))

    @mock.patch.object(base_model, 'logging', autospec=True)
    @mock.patch.object(search.Index, 'delete', autospec=True)
    @mock.patch.object(search.Index, 'get_range', autospec=True)
    def test_clear_index_delete_error(self, mock_range, mock_delete,
                                      mock_logging):
        mock_range.return_value = [
            search.ScoredDocument(doc_id='unused_doc_id')
        ]
        mock_delete.side_effect = search.DeleteError(message='Delete Fail!',
                                                     results=[])
        base_model.BaseModel.clear_index()
        self.assertEqual(mock_logging.exception.call_count, 1)

    def test_to_seach_fields(self):
        # Test list field generation.
        entity = TestEntity(test_repeatedprop=['item_1', 'item_2'])
        search_fields = entity._to_search_fields('test_repeatedprop',
                                                 ['item_1', 'item_2'])
        expected_fields = [
            search.TextField(name='test_repeatedprop', value='item_1'),
            search.AtomField(name='test_repeatedprop', value='item_1'),
            search.TextField(name='test_repeatedprop', value='item_2'),
            search.AtomField(name='test_repeatedprop', value='item_2')
        ]
        self.assertEqual(expected_fields, search_fields)

        # Test ndb.Key field generation.
        test_key = ndb.Key('Test', 1)
        entity = TestEntity(test_keyproperty=test_key)
        search_field = entity._to_search_fields('test_keyproperty', test_key)
        expected_field = [
            search.AtomField(name='test_keyproperty', value=test_key.urlsafe())
        ]
        self.assertEqual(expected_field, search_field)

        # Test datetime field generation.
        date = datetime.datetime(year=2017, month=1, day=5)
        entity = TestEntity(test_datetime=date)
        search_field = entity._to_search_fields('test_datetime', date)
        expected_field = [search.DateField(name='test_datetime', value=date)]
        self.assertEqual(expected_field, search_field)

        # Test boolean field generation.
        entity = TestEntity(test_bool=True)
        search_field = entity._to_search_fields('test_bool', True)
        expected_field = [search.AtomField(name='test_bool', value='True')]
        self.assertEqual(expected_field, search_field)

        # Test geopt field generation.
        geopt = ndb.GeoPt('52.37, 4.88')
        entity = TestEntity(test_geopt=geopt)
        search_field = entity._to_search_fields('test_geopt', geopt)
        expected_field = [
            search.GeoField(name='test_geopt',
                            value=search.GeoPoint(52.37, 4.88))
        ]
        self.assertEqual(expected_field, search_field)

    @mock.patch.object(base_model.BaseModel, '_to_search_fields')
    def test_get_document_fields(self, mock_to_search_fields):
        test_model = Test(text_field='item_1')
        text_field1 = search.TextField(name='text_field', value='item_1')
        test_field2 = search.TextField(name='identifier', value='name')
        expected_result = [text_field1, test_field2]
        mock_to_search_fields.side_effect = [[text_field1], [test_field2]]
        document_fields = test_model._get_document_fields()
        self.assertCountEqual(expected_result, document_fields)
        self.assertEqual(mock_to_search_fields.call_count, 2)

    @mock.patch.object(base_model.BaseModel, '_to_search_fields')
    def test_get_document_fields_no_identifier(self, mock_to_search_fields):
        test_model = TestSubEntity(test_substring='item_1')
        text_field1 = search.TextField(name='test_substring', value='item_1')
        test_field2 = search.TextField(name='test_subdatetime', value='name')
        expected_result = [text_field1, test_field2]
        mock_to_search_fields.side_effect = [[text_field1], [test_field2]]
        document_fields = test_model._get_document_fields()
        self.assertCountEqual(expected_result, document_fields)
        self.assertEqual(mock_to_search_fields.call_count, 2)

    @mock.patch.object(base_model.BaseModel,
                       '_get_document_fields',
                       autospec=True)
    def test_to_document(self, mock_get_document_fields):
        test_model = Test(id='fake')
        fields = [search.AtomField(name='text_field', value='12345ABC')]
        mock_get_document_fields.return_value = fields
        test_document = search.Document(doc_id=six.ensure_str(
            test_model.key.urlsafe()),
                                        fields=fields)
        result = test_model.to_document()
        self.assertEqual(result, test_document)

    @mock.patch.object(base_model.BaseModel,
                       '_get_document_fields',
                       return_value=False,
                       autospec=True)
    def test_to_document_error(self, mock_get_document_fields):
        test_model = Test(id='fake')
        with self.assertRaises(base_model.DocumentCreationError):
            test_model.to_document()

    def test_search(self):
        index = Test.get_index()
        test_doc = search.Document(
            doc_id='test_doc_id_1',
            fields=[search.TextField(name='text_field', value='item 1')])
        not_used_doc = search.Document(
            doc_id='test_doc_id_2',
            fields=[search.TextField(name='text_field', value='item 2')])
        not_used_doc2 = search.Document(
            doc_id='test_doc_id_3',
            fields=[search.TextField(name='text_field', value='notused')])
        index.put(test_doc)
        index.put(not_used_doc)
        index.put(not_used_doc2)
        actual_search_result = Test.search(query_string='item', query_limit=1)
        self.assertEqual(actual_search_result.number_found, 2)
        self.assertLen(actual_search_result.results, 1)

        no_search_results = Test.search(query_string='does_not_exist')
        self.assertEqual(no_search_results.number_found, 0)

    @mock.patch.object(search, 'Query', autospec=True)
    def test_search_query_error(self, mock_query):
        mock_query.side_effect = search.QueryError
        search_result = Test.search(query_string='item1')
        self.assertIsInstance(search_result, search.SearchResults)

    @parameterized.parameters(
        (
            'a:123456',
            'asset_tag:123456',
        ),
        (
            'at:123456',
            'asset_tag:123456',
        ),
        (
            's:123456ABC',
            'serial_number:123456ABC',
        ),
        (
            'sn:123456ABC',
            'serial_number:123456ABC',
        ),
        (
            'u:user',
            'assigned_user:user',
        ),
        (
            'au:user',
            'assigned_user:user',
        ),
        (
            'j:not_in_params',
            'j:not_in_params',
        ),  # Not within the search params.
        (
            '123456',
            '123456',
        ),  # Query string does not need formatting.
        (None, ''))  # None should return an empty string.
    def test_format_query(self, test_query_string, expected_query_string):
        Test._SEARCH_PARAMETERS = {
            'a': 'asset_tag',
            'at': 'asset_tag',
            's': 'serial_number',
            'sn': 'serial_number',
            'u': 'assigned_user',
            'au': 'assigned_user'
        }

        formatted_query_string = Test.format_query(test_query_string)
        self.assertEqual(expected_query_string, formatted_query_string)