diff --git a/java-app/get_sdk.sh b/java-app/get_sdk.sh old mode 100644 new mode 100755 index 69c6c1e..d4d662e --- a/java-app/get_sdk.sh +++ b/java-app/get_sdk.sh @@ -1,15 +1,28 @@ +#!/bin/bash + JAVA_SDK_VERSION=1.8.4 JAVA_SDK_ZIP=appengine-java-sdk-${JAVA_SDK_VERSION}.zip +SDK_URL="http://s3.amazonaws.com/appscale-build/${JAVA_SDK_ZIP}" cd ../../ if [ -e ${HOME}/${JAVA_SDK_ZIP} ]; then cp ${HOME}/${JAVA_SDK_ZIP} . else - wget http://googleappengine.googlecode.com/files/${JAVA_SDK_ZIP} + if ! wget ${SDK_URL}; then + echo "Failed to retrieve SDK from ${SDK_URL}, exiting script" + cd - + exit 1 + fi fi -echo "Extracting ${JAVA_SDK_ZIP}" -unzip -q ${JAVA_SDK_ZIP} -rm ${JAVA_SDK_ZIP} +echo "Extracting ${JAVA_SDK_ZIP} to ${PWD}" + +if ! unzip -q ${JAVA_SDK_ZIP}; then + echo "Failed to unzip SDK correctly, please see errors above" + rm -f ${JAVA_SDK_ZIP} + cd - + exit 1 +fi +rm -f ${JAVA_SDK_ZIP} cd - diff --git a/python27-app/module-main/app.yaml b/python27-app/module-main/app.yaml index af85ff6..9b029e1 100644 --- a/python27-app/module-main/app.yaml +++ b/python27-app/module-main/app.yaml @@ -83,6 +83,9 @@ handlers: - url: /_ah/xmpp/message/chat/? script: main.app +- url: /python/search/(.*) + script: main.app + - url: /.* script: main.app diff --git a/python27-app/module-main/main.py b/python27-app/module-main/main.py index aa69650..6f85b52 100644 --- a/python27-app/module-main/main.py +++ b/python27-app/module-main/main.py @@ -11,6 +11,7 @@ from memcache import urls as memcache_urls from module_main import urls as modules_urls from ndb import urls as ndb_urls +from search import urls as search_urls from secure_url import urls as secure_url_urls from taskqueue import urls as taskqueue_urls from urlfetch import urls as urlfetch_urls @@ -33,5 +34,6 @@ taskqueue_urls + urlfetch_urls + user_urls + - xmpp_urls + xmpp_urls + + search_urls ) diff --git a/python27-app/module-main/search.py b/python27-app/module-main/search.py new file mode 100755 index 0000000..8d21b31 --- /dev/null +++ b/python27-app/module-main/search.py @@ -0,0 +1,149 @@ +import json +import datetime +import logging +import wsgiref + +import webapp2 + +from google.appengine.api.search import search +from google.appengine.api.search.search import QueryOptions, ScoredDocument, Query +from google.appengine.ext import webapp + +field_type_dict = {'text':search.TextField, + 'html':search.HtmlField, + 'atom':search.AtomField, + 'number':search.NumberField, + 'date':search.DateField} + +def get_field(field_name): + return field_type_dict[field_name] + +def render_doc(document): + """ + Generates JSON-compatible object from object of type search.Document + All fields are expected to be TextField for now + :param document: document to render + :type document: search.Document + :return: dict {: } + """ + if not document: + return None + document_dict = { + field.name: unicode(field.value) for field in document.fields + } + # Pull over document id as it isn't included in fields. + document_dict['id'] = document.doc_id + + if isinstance(document, ScoredDocument): + document_dict["_sort_scores"] = document.sort_scores + return document_dict + + +def parse_doc(raw_document): + """ + Builds object of type search.Document from dict + Only TextFields are supported for now + :param raw_document: {: } + :return: search.Document + """ + fields = [] + # make sure fields exists? + for f in raw_document.get('fields'): + + field = get_field(f['type']) # get class by simple name: text,atom + if f['type'] in ['text','html']: + # text/html has a lang field, lang should be a two character + # specifier as required by the sdk. + fields.append(field(f['name'], + f['value'], + f['lang'] if 'lang' in f else 'en')) + elif f['type'] == 'date': + # for date field we need to convert to a datetime + fields.append(field(f['name'], + datetime.datetime.strptime(f['value'], '%Y-%m-%d'))) + else: + # All other fields just have a name/value pair. + fields.append(field(f['name'], + f['value'])) + + return search.Document( + doc_id=raw_document.get('id'), + fields=fields + ) + + + +class PutDocumentsHandler(webapp2.RequestHandler): + + def post(self): + payload = json.loads(self.request.body) + index = payload['index'] + raw_documents = payload['documents'] + documents = [parse_doc(raw_doc) for raw_doc in raw_documents] + put_results = search.Index(name=index).put(documents) + response = {'document_ids': [result.id for result in put_results]} + self.response.headers['Content-Type'] = 'application/json' + self.response.out.write(json.dumps(response)) + + +class GetDocumentHandler(webapp2.RequestHandler): + + def post(self): + payload = json.loads(self.request.body) + index = payload['index'] + doc_id = payload['id'] + document = search.Index(name=index).get(doc_id) + response = {'document': render_doc(document)} + self.response.headers['Content-Type'] = 'application/json' + self.response.out.write(json.dumps(response)) + + +class GetDocumentsRangeHandler(webapp2.RequestHandler): + + def post(self): + payload = json.loads(self.request.body) + index = payload['index'] + start_id = payload['start_id'] + limit = payload.get('limit') + index = search.Index(name=index) + documents = index.get_range(start_id=start_id, limit=limit) + response = {'documents': [render_doc(document) for document in documents]} + self.response.headers['Content-Type'] = 'application/json' + self.response.out.write(json.dumps(response)) + + +class SearchDocumentsHandler(webapp2.RequestHandler): + + def post(self): + payload = json.loads(self.request.body) + index = payload['index'] + query = payload['query'] + cursor = payload.get('cursor') + limit = payload.get('limit', 20) + index = search.Index(name=index) + query_options = QueryOptions(limit=limit, cursor=cursor) + result = index.search(Query(query, options=query_options)) + response = {'documents': [render_doc(document) for document in result], + 'cursor': result.cursor} + self.response.headers['Content-Type'] = 'application/json' + self.response.out.write(json.dumps(response)) + + +class CleanUpHandler(webapp2.RequestHandler): + + def post(self): + payload = json.loads(self.request.body) + document_ids = payload['document_ids'] + index = payload['index'] + idx = search.Index(name=index) + + idx.delete(document_ids) + idx.delete_schema() + +urls = [ + ('/python/search/put', PutDocumentsHandler), + ('/python/search/get', GetDocumentHandler), + ('/python/search/get-range', GetDocumentsRangeHandler), + ('/python/search/search', SearchDocumentsHandler), + ('/python/search/clean-up', CleanUpHandler), +] diff --git a/test-suite/hawkeye.py b/test-suite/hawkeye.py index 8d4c229..39e9b18 100755 --- a/test-suite/hawkeye.py +++ b/test-suite/hawkeye.py @@ -38,7 +38,8 @@ app_identity_tests, datastore_tests, ndb_tests, memcache_tests, taskqueue_tests, blobstore_tests, user_tests, images_tests, secure_url_tests, xmpp_tests, environment_variable_tests, async_datastore_tests, cron_tests, - logservice_tests, modules_tests, runtime_tests, urlfetch_tests, warmup_tests + logservice_tests, modules_tests, runtime_tests, urlfetch_tests, warmup_tests, + search_tests ) SUPPORTED_LANGUAGES = ['java', 'python'] @@ -77,7 +78,8 @@ def build_suites_list(lang, include, exclude, application): 'cron' : cron_tests.suite(lang, application), 'logservice': logservice_tests.suite(lang, application), 'modules' : modules_tests.suite(lang, application), - 'runtime': runtime_tests.suite(lang, application) + 'runtime': runtime_tests.suite(lang, application), + 'search': search_tests.suite(lang, application), } # Validation include and exclude lists for suite_name in include + exclude: diff --git a/test-suite/hawkeye_baseline_python.csv b/test-suite/hawkeye_baseline_python.csv index dc27f9a..06c88d1 100644 --- a/test-suite/hawkeye_baseline_python.csv +++ b/test-suite/hawkeye_baseline_python.csv @@ -141,3 +141,29 @@ tests.taskqueue_tests.TransactionalTaskTest.runTest,ok tests.urlfetch_tests.CertificateValidation.runTest,ok tests.user_tests.LoginURLTest.runTest,ok tests.xmpp_tests.SendAndReceiveTest.runTest,ok +tests.search_tests.PutTest.test_search_put,ok +tests.search_tests.GetTest.test_search_get,ok +tests.search_tests.GetRangeTest.test_get_range_all_documents,ok +tests.search_tests.GetRangeTest.test_get_range_three_documents_from_a,ok +tests.search_tests.GetRangeTest.test_get_range_three_documents_from_document_c,ok +tests.search_tests.SearchTest.test_search_query_AND_no_results,ok +tests.search_tests.SearchTest.test_search_query_AND_two_results,ok +tests.search_tests.SearchTest.test_search_query_OR,ok +tests.search_tests.SearchTest.test_search_query_date_equal_by_field,ok +tests.search_tests.SearchTest.test_search_query_date_global_equal,ok +tests.search_tests.SearchTest.test_search_query_date_less_than,ok +tests.search_tests.SearchTest.test_search_query_implicit_AND_two_results,ok +tests.search_tests.SearchTest.test_search_query_multivalue_fields_first_value,ok +tests.search_tests.SearchTest.test_search_query_multivalue_fields_number_no_results,ok +tests.search_tests.SearchTest.test_search_query_multivalue_fields_second_value,ok +tests.search_tests.SearchTest.test_search_query_multivalue_fields_string_no_results,ok +tests.search_tests.SearchTest.test_search_query_multivalue_fields_third_value,ok +tests.search_tests.SearchTest.test_search_query_number_by_field_equal,ok +tests.search_tests.SearchTest.test_search_query_number_by_field_equal_colon,ok +tests.search_tests.SearchTest.test_search_query_number_greater_than_equal_equality,ok +tests.search_tests.SearchTest.test_search_query_number_greater_than_equal_one,ok +tests.search_tests.SearchTest.test_search_query_number_greater_than_equal_two,ok +tests.search_tests.SearchTest.test_search_query_number_less_than,ok +tests.search_tests.SearchTest.test_search_query_number_less_than_equal,ok +tests.search_tests.SearchTest.test_search_query_number_less_than_no_results,ok +tests.search_tests.SearchTest.test_search_simple_query,ok diff --git a/test-suite/hawkeye_test_runner.py b/test-suite/hawkeye_test_runner.py index 23f380a..2ffc42b 100644 --- a/test-suite/hawkeye_test_runner.py +++ b/test-suite/hawkeye_test_runner.py @@ -209,7 +209,7 @@ def load_report_dict_from_csv(file_name): A dictionary with statuses of tests (: ). """ with open(file_name, "r") as csv_file: - return {test_id: result for test_id, result in csv.reader(csv_file)} + return {test_id: result.rstrip() for test_id, result in csv.reader(csv_file)} class ReportsDiff(object): diff --git a/test-suite/tests/search_tests.py b/test-suite/tests/search_tests.py new file mode 100644 index 0000000..73d7417 --- /dev/null +++ b/test-suite/tests/search_tests.py @@ -0,0 +1,578 @@ +import time + +from hawkeye_test_runner import HawkeyeTestCase, HawkeyeTestSuite + +# GAE uses eventual consistency for SearchAPI +CONSISTENCY_WAIT_TIME = 0.5 + +default_documents = { + "index": "index-1", + "documents": [ + { + "id": "a", + "fields": [{"name": "text1", + "type": "text", + "lang": "en", + "value": "hello world"}, + {"name": "text2", + "lang": "en", + "value": "testing search api and world", + "type": "text"}] + }, + { + "id": "b", + "fields": [{"name": "anotherText", + "value": "There is also a corresponding asynchronous method, ", + "type": "text"}] + }, + { + "id": "c", + "fields": [{"name": "text3", + "value": "This string also contains the word world and api", + "type": "text"}] + }, + { + "id": "firstdate", + "fields": [{"name": "date_entered", + "type": "date", + "value": "2018-12-12"}, + {"name": "text4", + "value": "This is a test of the national broadcasting system", + "type": "text"}] + }, + { + "id": "seconddate", + "fields": [{"name": "date_entered", + "value": "2019-04-04", + "type": "date"}, + {"name": "text5", + "value": "Who are the britons?", + "type": "text"}] + }, + { + "id": "importantnumbers", + "fields": [{"name": "meaning_of_life", + "value": 42, + "type": "number"}, + {"name": "holyhandgrenadecount", + "value": 3, + "type": "number"}] + }, + { + "id": "numbers", + "fields": [{"name": "meeting_timeout", + "value": 15, + "type": "number"}, + {"name": "fiveisrightout", + "value": 5, + "type": "number"}, + {"name": "hourly_rate", + "value": 4, + "type": "number"}] + }, + { + "id": "numbers2", + "fields": [{"name": "meeting_timeout", + "value": 35, + "type": "number"}, + {"name": "hourly_rate", + "value": 2, + "type": "number"}] + }, + { + "id": "multivalue", + "fields": [{"name": "mfield", + "value": "stringfield1", + "type": "text"}, + {"name": "mfield", + "value": "stringfield2", + "type": "text"}, + {"name": "mfield", + "value": 22, + "type": "number"}] + } + ] +} + +# Documents that will need to be deleted from the index +document_ids = {"index": "index-1", + "document_ids": [i["id"] + for i in + default_documents["documents"]]} + + +class SearchTestCase(HawkeyeTestCase): + + def tearDown(self): + """ + Tear down of the test will remove all documents from the index + and clear the index_schema + """ + self.app.post("/python/search/clean-up", json=document_ids) + + +class PutTest(SearchTestCase): + + def test_search_put(self): + response = self.app.post("/python/search/put", json=default_documents) + self.assertEquals(response.status_code, 200) + + # Account for 'eventual consistency' of the SearchAPI + # by allowing time for the document to propagate through + # the system + time.sleep(CONSISTENCY_WAIT_TIME) + + +class GetTest(SearchTestCase): + + def setUp(self): + self.app.post("/python/search/put", json=default_documents) + time.sleep(CONSISTENCY_WAIT_TIME) + + def test_search_get(self): + response = self.app.post( + "/python/search/get", + json={"index": "index-1", "id": "a"} + ) + self.assertEquals(response.status_code, 200) + + doc = response.json()["document"] + self.assertEquals("a", doc["id"]) + self.assertIn("text1", doc) + self.assertIn("text2", doc) + + +class GetRangeTest(SearchTestCase): + + def setUp(self): + self.app.post("/python/search/put", json=default_documents) + time.sleep(CONSISTENCY_WAIT_TIME) + + def test_get_range_three_documents_from_a(self): + """ + Range request starting at the first document "a". + Should get "a" "b" "c" + Note: It isn't clear what "first" implies in GAE... + It looks as though 'first' implies the + first document id in *sorted* order. + """ + response = self.app.post( + "/python/search/get-range", + json={"index": "index-1", "start_id": "a", "limit": 3} + ) + self.assertEquals(response.status_code, 200) + + documents = response.json()["documents"] + self.assertEquals(len(documents), 3) + + ids = [doc["id"] for doc in documents] + self.assertIn("a", ids) + self.assertIn("b", ids) + self.assertIn("c", ids) + + def test_get_range_three_documents_from_c(self): + """ + Range request starting at the first document "c". + Should get "c" "firstdate" "importantnumbers" + """ + response = self.app.post( + "/python/search/get-range", + json={"index": "index-1", "start_id": "c", "limit": 3} + ) + self.assertEquals(response.status_code, 200) + + documents = response.json()["documents"] + self.assertEquals(len(documents), 3) + + ids = [doc["id"] for doc in documents] + self.assertIn("c", ids) + self.assertIn("firstdate", ids) + self.assertIn("importantnumbers", ids) + + def test_get_range_all_documents(self): + """ + Range request, set limit to 100, should get all documents back + """ + response = self.app.post( + "/python/search/get-range", + json={"index": "index-1", "start_id": "a", "limit": 100} + ) + self.assertEquals(response.status_code, 200) + + documents = response.json()["documents"] + self.assertEquals(len(documents), len(default_documents["documents"])) + + +class SearchTest(SearchTestCase): + + def setUp(self): + self.app.post("/python/search/put", json=default_documents) + time.sleep(CONSISTENCY_WAIT_TIME) + + def test_search_simple_query(self): + """ + Test a simple global query with one keyword + """ + response = self.app.post( + "/python/search/search", + json={"index": "index-1", "query": "hello"} + ) + self.assertEquals(response.status_code, 200) + response_json = response.json() + self.assertIn("documents", response_json) + documents = response_json["documents"] + self.assertEqual(len(documents), 1) + self.assertIn("text1",documents[0]) + self.assertIn("text2",documents[0]) + + def test_search_query_AND_no_results(self): + """ + Query using AND that should result in no documents + """ + response = self.app.post( + "/python/search/search", + json={"index": "index-1", "query": "hello AND corresponding"} + ) + + self.assertEquals(response.status_code, 200) + self.assertIn("documents", response.json()) + self.assertEquals(len(response.json()["documents"]), 0) + + def test_search_query_AND_two_results(self): + """ + Query using AND that should result in multiple documents + """ + response = self.app.post( + "/python/search/search", + json={"index": "index-1", "query": "world AND api"} + ) + + self.assertEquals(response.status_code, 200) + self.assertIn("documents", response.json()) + documents = response.json()["documents"] + self.assertEquals(len(documents), 2) + + # Documents "a" and "c" should be in the results as they + # both have "api, world" in the content + ids = [doc["id"] for doc in documents] + self.assertIn("a", ids) + self.assertIn("c", ids) + + def test_search_query_implicit_AND_two_results(self): + """ + Implicit AND query that should result in multiple documents + """ + response = self.app.post( + "/python/search/search", + json={"index": "index-1", "query": "world api"} + ) + + self.assertEquals(response.status_code, 200) + self.assertIn("documents", response.json()) + documents = response.json()["documents"] + self.assertEquals(len(documents), 2) + + # Documents "a" and "c" should be in the results as they + # both have "api, world" in the content + ids = [doc["id"] for doc in documents] + self.assertIn("a", ids) + self.assertIn("c", ids) + + def test_search_query_OR(self): + """ + OR query that should result in 2 documents + """ + response = self.app.post( + "/python/search/search", + json={"index": "index-1", "query": "hello OR britons"} + ) + + self.assertEquals(response.status_code, 200) + self.assertIn("documents", response.json()) + documents = response.json()["documents"] + self.assertEquals(len(documents), 2) + + ids = [doc["id"] for doc in documents] + self.assertIn("a", ids) + self.assertIn("seconddate", ids) + + def test_search_query_date_global_equal(self): + """ + Date query without field name + """ + response = self.app.post( + "/python/search/search", + json={"index": "index-1", "query": "2018-12-12"} + ) + + self.assertEquals(response.status_code, 200) + self.assertIn("documents", response.json()) + documents = response.json()["documents"] + # Should only be one document + self.assertEquals(len(documents), 1) + doc = documents[0] + # Make sure we got the right document back with that date. + self.assertEquals(doc["date_entered"], "2018-12-12 00:00:00") + self.assertEquals(doc["text4"], "This is a test of the national broadcasting system") + + def test_search_query_date_equal_by_field(self): + """ + Date query by field name + """ + response = self.app.post( + "/python/search/search", + json={"index": "index-1", "query": "date_entered: 2019-04-04"} + ) + self.assertEquals(response.status_code, 200) + self.assertIn("documents", response.json()) + + documents = response.json()["documents"] + self.assertEquals(len(documents), 1) + + doc = documents[0] + self.assertEquals(doc["date_entered"], "2019-04-04 00:00:00") + self.assertEquals(doc["text5"], "Who are the britons?") + + def test_search_query_date_less_than(self): + """ + Date less than query + """ + response = self.app.post( + "/python/search/search", + json={"index": "index-1", "query": "date_entered < 2019-04-04"} + ) + self.assertEquals(response.status_code, 200) + self.assertIn("documents", response.json()) + + documents = response.json()["documents"] + self.assertEquals(len(documents), 1) + + doc = documents[0] + self.assertEquals(doc["date_entered"], "2018-12-12 00:00:00") + self.assertEquals(doc["text4"], "This is a test of the national broadcasting system") + + def test_search_query_number_by_field_equal(self): + """ + Number query where number is equal, specified by '=' + """ + response = self.app.post( + "/python/search/search", + json={"index": "index-1", "query": "meaning_of_life = 42"} + ) + self.assertEquals(response.status_code, 200) + self.assertIn("documents", response.json()) + + documents = response.json()["documents"] + self.assertEquals(len(documents), 1) + + doc = documents[0] + self.assertEquals(doc["id"], "importantnumbers") + self.assertEquals(doc["meaning_of_life"], u'42.0') + + def test_search_query_number_by_field_equal_colon(self): + """ + Number query where number is equal, specified by colon + """ + response = self.app.post( + "/python/search/search", + json={"index": "index-1", "query": "meaning_of_life: 42"} + ) + self.assertEquals(response.status_code, 200) + self.assertIn("documents", response.json()) + + documents = response.json()["documents"] + self.assertEquals(len(documents), 1) + + doc = documents[0] + self.assertEquals(doc["id"], "importantnumbers") + self.assertEquals(doc["meaning_of_life"], u'42.0') + + def test_search_query_number_less_than(self): + """ + Number query for meaning_of_life less than 50 + """ + response = self.app.post( + "/python/search/search", + json={"index": "index-1", "query": "meaning_of_life < 50"} + ) + self.assertEquals(response.status_code, 200) + self.assertIn("documents", response.json()) + + documents = response.json()["documents"] + self.assertEquals(len(documents), 1) + + doc = documents[0] + self.assertEquals(doc["id"], "importantnumbers") + self.assertEquals(doc["meaning_of_life"], u'42.0') + + def test_search_query_number_less_than_no_results(self): + """ + Number query for meaning_of_life less than 42 (no docs) + """ + response = self.app.post( + "/python/search/search", + json={"index": "index-1", "query": "meaning_of_life < 42"} + ) + self.assertEquals(response.status_code, 200) + self.assertIn("documents", response.json()) + + documents = response.json()["documents"] + self.assertEquals(len(documents), 0) + + def test_search_query_number_less_than_equal(self): + """ + Number query for hourly_rate <= 4 + """ + response = self.app.post( + "/python/search/search", + json={"index": "index-1", "query": "hourly_rate <=4"} + ) + self.assertEquals(response.status_code, 200) + self.assertIn("documents", response.json()) + + documents = response.json()["documents"] + self.assertEquals(len(documents), 2) + + docs = [i["id"] for i in documents] + self.assertIn("numbers", docs) + self.assertIn("numbers2", docs) + + def test_search_query_number_greater_than_equal_equality(self): + """ + Number query for hourly_rate >= 4, 4 being an exact equality match + """ + response = self.app.post( + "/python/search/search", + json={"index": "index-1", "query": "hourly_rate >= 4"} + ) + self.assertEquals(response.status_code, 200) + self.assertIn("documents", response.json()) + + documents = response.json()["documents"] + self.assertEquals(len(documents), 1) + + docs = [i["id"] for i in documents] + self.assertIn("numbers", docs) + + def test_search_query_number_greater_than_equal_one(self): + """ + Number query for hourly_rate >= 3 - should return 1 doc + """ + response = self.app.post( + "/python/search/search", + json={"index": "index-1", "query": "hourly_rate >= 3"} + ) + self.assertEquals(response.status_code, 200) + self.assertIn("documents", response.json()) + + documents = response.json()["documents"] + self.assertEquals(len(documents), 1) + + docs = [i["id"] for i in documents] + self.assertIn("numbers", docs) + + def test_search_query_number_greater_than_equal_two(self): + """ + Number query for hourly_rate >= 2 - should return 2 docs + """ + response = self.app.post( + "/python/search/search", + json={"index": "index-1", "query": "hourly_rate >= 2"} + ) + self.assertEquals(response.status_code, 200) + self.assertIn("documents", response.json()) + + documents = response.json()["documents"] + self.assertEquals(len(documents), 2) + + docs = [i["id"] for i in documents] + self.assertIn("numbers", docs) + self.assertIn("numbers2", docs) + + def test_search_query_multivalue_fields_first_value(self): + """ + Multivalue field test match on first value + """ + response = self.app.post( + "/python/search/search", + json={"index": "index-1", "query": "mfield: stringfield1"} + ) + self.assertEquals(response.status_code, 200) + self.assertIn("documents", response.json()) + + documents = response.json()["documents"] + self.assertEquals(len(documents), 1) + + doc = documents[0] + self.assertEquals("multivalue", doc["id"]) + + def test_search_query_multivalue_fields_second_value(self): + """ + Multivalue field test match on second value + """ + response = self.app.post( + "/python/search/search", + json={"index": "index-1", "query": "mfield: stringfield2"} + ) + self.assertEquals(response.status_code, 200) + self.assertIn("documents", response.json()) + + documents = response.json()["documents"] + self.assertEquals(len(documents), 1) + + doc = documents[0] + self.assertEquals("multivalue", doc["id"]) + + def test_search_query_multivalue_fields_third_value(self): + """ + Multivalue field test match on third value, different type + """ + response = self.app.post( + "/python/search/search", + json={"index": "index-1", "query": "mfield: 22"} + ) + self.assertEquals(response.status_code, 200) + self.assertIn("documents", response.json()) + + documents = response.json()["documents"] + self.assertEquals(len(documents), 1) + + doc = documents[0] + self.assertEquals("multivalue", doc["id"]) + + def test_search_query_multivalue_fields_string_no_results(self): + """ + Multivalue field test no match on string + """ + response = self.app.post( + "/python/search/search", + json={"index": "index-1", "query": "mfield: notfound"} + ) + self.assertEquals(response.status_code, 200) + self.assertIn("documents", response.json()) + + documents = response.json()["documents"] + self.assertEquals(len(documents), 0) + + def test_search_query_multivalue_fields_number_no_results(self): + """ + Multivalue field test no match on number + """ + response = self.app.post( + "/python/search/search", + json={"index": "index-1", "query": "mfield: 10000"} + ) + self.assertEquals(response.status_code, 200) + self.assertIn("documents", response.json()) + + documents = response.json()["documents"] + self.assertEquals(len(documents), 0) + + +def suite(lang, app): + suite = HawkeyeTestSuite("Search API Test Suite", "search") + suite.addTests(PutTest.all_cases(app)) + suite.addTests(GetTest.all_cases(app)) + suite.addTests(GetRangeTest.all_cases(app)) + suite.addTests(SearchTest.all_cases(app)) + return suite