Skip to content
This repository has been archived by the owner on Jun 12, 2018. It is now read-only.

Commit

Permalink
Merge pull request #809 from praekelt/feature/issue-809-contacts-reso…
Browse files Browse the repository at this point in the history
…urce-search

Implemented contact search API for the JS Sandbox
  • Loading branch information
vgeddes committed Dec 19, 2013
2 parents 4c2b57a + 3400688 commit 9d50cb8
Show file tree
Hide file tree
Showing 2 changed files with 231 additions and 0 deletions.
126 changes: 126 additions & 0 deletions go/apps/jsbox/contacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,132 @@ def handle_save(self, api, command):
success=True,
contact=contact.get_data()))

@inlineCallbacks
def handle_get_by_key(self, api, command):
"""
Retrieve a contact object by key
Command fields:
- ``key``: The key identifying an existing contact.
Success reply fields:
- ``success``: set to ``true``
- ``contact``: An object containing the contact's data
Failure reply fields:
- ``success``: set to ``false``
- ``reason``: A string describing the reason for the failure
Examples:
Retrieve a contact which is known to have key
'391cea45-ae51-441c-b972-5de765c7a0dc'.
.. code-block:: javascript
api.request(
'contacts.get_by_key', {
key: '391cea45-ae51-441c-b972-5de765c7a0dc',
},
function(reply) { api.log_info(reply.contact); });
"""
try:
if 'key' not in command:
returnValue(self.reply(
command,
success=False,
reason=u"Expected 'key' field in request"))

key = command['key']
contact_store = self._contact_store_for_api(api)
contact = (yield contact_store.get_contact_by_key(key)).get_data()

except (ContactNotFoundError,) as e:
returnValue(self.reply(command, success=False, reason=unicode(e)))
except (ContactError, SandboxError,) as e:
log.warning(str(e))
returnValue(self.reply(command, success=False, reason=unicode(e)))

returnValue(self.reply(
command,
success=True,
contact=contact))

@inlineCallbacks
def handle_search(self, api, command):
"""
Search for contacts
Command fields:
- ``query``: The Lucene search query to perform.
- ``max_keys``: If present, a non-negative number that specifies
the maximum number of keys to return in the result.
By default keys for all matching contacts are
returned.
Success reply fields:
- ``success``: set to ``true``
- ``keys``: A list of keys for matching contacts.
Note: If no matches are found ``keys`` will be an empty list.
Failure reply fields:
- ``success``: set to ``false``
- ``reason``: Reason for the failure
Examples:
Searching on a single contact field:
.. code-block:: javascript
api.request(
'contacts.search', {
query: 'name:"My Name"',
},
function(reply) { api.log_info(reply.keys); });
"""
try:
if 'query' not in command:
returnValue(self.reply(
command,
success=False,
reason=u"Expected 'query' field in request"))
max_keys = None
if 'max_keys' in command:
value = command['max_keys']
if type(value) is int and value >= 0:
max_keys = value
else:
returnValue(self.reply(
command,
success=False,
reason=u"Value for parameter 'max_keys' is invalid"))

contact_store = self._contact_store_for_api(api)
keys = yield contact_store.contacts.raw_search(
command['query']).get_keys()
if max_keys is not None:
keys = keys[:max_keys]

except (SandboxError,) as e:
log.warning(str(e))
returnValue(self.reply(command, success=False, reason=unicode(e)))
except (Exception,) as e:
# NOTE: Hello Riakasaurus, you raise horribly plain exceptions on
# a MapReduce error.
if 'MapReduce' not in str(e):
raise
log.warning(str(e))
returnValue(self.reply(command, success=False, reason=unicode(e)))

returnValue(self.reply(
command,
success=True,
keys=keys))


class GroupsResource(SandboxResource):
"""
Expand Down
105 changes: 105 additions & 0 deletions go/apps/jsbox/tests/test_contacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,111 @@ def test_handle_save_for_unicode_chars(self):
def test_handle_save_for_nonexistent_contacts(self):
return self.assert_bad_command('save', contact={'key': u'213123'})

@inlineCallbacks
def test_handle_search(self):
contact = yield self.new_contact(
surname=u'Jackal',
msisdn=u'+27831234567',
groups=[u'group-a', u'group-b'])
reply = yield self.dispatch_command('search', query=u'surname:Jack*')
self.assertTrue(reply['success'])
self.assertFalse('reason' in reply)

self.assertTrue('keys' in reply)
self.assertEqual(reply['keys'][0], contact.key)

@inlineCallbacks
def test_handle_search_bad_query(self):
reply = yield self.dispatch_command(
'search', query=u'name:[BAD_QUERY!]')
self.assertFalse(reply['success'])
self.assertFalse('keys' in reply)
self.assertTrue('reason' in reply)

self.assertTrue('Error running MapReduce' in reply['reason'])

@inlineCallbacks
def test_handle_search_results(self):
reply = yield self.dispatch_command('search', query=u'name:foo*')
self.assertTrue(reply['success'])
self.assertFalse('reason' in reply)
self.assertEqual(reply['keys'], [])

@inlineCallbacks
def test_handle_search_missing_param(self):
reply = yield self.dispatch_command('search')
self.assertFalse(reply['success'])
self.assertTrue('reason' in reply)
self.assertFalse('keys' in reply)
self.assertTrue("Expected 'query' field in request" in reply['reason'])

@inlineCallbacks
def test_handle_search_max_keys(self):
keys = set()
for i in range(0, 6):
contact = yield self.new_contact(
surname=unicode('Jackal%s' % i),
msisdn=u'+27831234567',
groups=[u'group-a', u'group-b'])
keys.add(contact.key)

# subset
reply = yield self.dispatch_command('search',
query=u'surname:Jack*',
max_keys=3)
self.assertTrue(reply['success'])
self.assertEqual(len(reply['keys']), 3)
self.assertTrue(set(reply['keys']).issubset(keys))

# no limit
reply = yield self.dispatch_command('search',
query=u'surname:Jack*')
self.assertTrue(reply['success'])
self.assertEqual(set(reply['keys']), keys)

# bad value for max_keys
reply = yield self.dispatch_command('search',
query=u'surname:Jack*',
max_keys="Haha!")
self.assertFalse(reply['success'])
self.assertTrue(
"Value for parameter 'max_keys' is invalid" in reply['reason']
)

@inlineCallbacks
def test_handle_get_by_key(self):
contact = yield self.new_contact(
surname=u'Jackal',
msisdn=u'+27831234567',
groups=[u'group-a', u'group-b'])

reply = yield self.dispatch_command('get_by_key', key=contact.key)

self.assertTrue(reply['success'])
self.assertFalse('reason' in reply)
self.assertTrue('contact' in reply)

self.assertEqual(reply['contact']['key'], contact.key)

@inlineCallbacks
def test_handle_get_by_key_missing_param(self):
reply = yield self.dispatch_command('get_by_key')
self.assertFalse(reply['success'])
self.assertTrue('reason' in reply)
self.assertFalse('contact' in reply)
self.assertTrue("Expected 'key' field in request" in reply['reason'])

@inlineCallbacks
def test_handle_get_by_key_no_results(self):
reply = yield self.dispatch_command('get_by_key', key="Haha!")
self.assertFalse(reply['success'])
self.assertTrue('reason' in reply)
self.assertFalse('contact' in reply)

self.assertTrue(
"Contact with key 'Haha!' not found." in reply['reason']
)


class TestGroupsResource(ResourceTestCaseBase, GoPersistenceMixin):
use_riak = True
Expand Down

0 comments on commit 9d50cb8

Please sign in to comment.