Skip to content
This repository has been archived by the owner on Oct 11, 2022. It is now read-only.

Add getting contact by field #29

Merged
merged 17 commits into from
Feb 9, 2015
Merged
17 changes: 14 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,28 @@ language: python
python:
- "2.6"
- "2.7"
services:
- riak
before_install:
# we need the protobuf-compiler so we can install Riak client libraries
# We need Riak 1.4, so we add Basho's repo and install from there.
# Additionally, we remove existing Riak data and replace the config with ours.
- sudo service riak stop
- "curl http://apt.basho.com/gpg/basho.apt.key | sudo apt-key add -"
- sudo bash -c "echo deb http://apt.basho.com $(lsb_release -sc) main > /etc/apt/sources.list.d/basho.list"
- sudo apt-get -qq update
- sudo apt-get install -qq -y --force-yes riak=1.4.12-1
- sudo rm -rf /var/lib/riak/*
- sudo cp utils/app.config /etc/riak/app.config
- sudo service riak start
# We need the protobuf-compiler so we can install Riak client libraries.
- sudo apt-get install -qq protobuf-compiler
install:
- "pip install -r requirements.txt --use-wheel"
- "pip install coveralls --use-wheel"
- "pip install -e ."
# We need to install the verified fake as well so we can test it.
- "pip install -e ./verified-fake"
# To see what version of Riak we're running and check that it's happy.
- riak version
- riak-admin member-status
script:
- coverage run --source=go_contacts `which trial` go_contacts
after_success:
Expand Down
35 changes: 33 additions & 2 deletions go_contacts/backends/contacts.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
Riak contacts backend and collection.
"""
from collections import defaultdict

from twisted.internet.defer import inlineCallbacks, returnValue
from zope.interface import implementer
Expand All @@ -9,6 +10,7 @@

from go.vumitools.contact import (
ContactStore, ContactNotFoundError, Contact)
from go.vumitools.contact.models import DELIVERY_CLASSES

from go_api.collections import ICollection
from go_api.collections.errors import (
Expand Down Expand Up @@ -52,6 +54,7 @@ class RiakContactsCollection(object):
def __init__(self, contact_store, max_contacts_per_page):
self.contact_store = contact_store
self.max_contacts_per_page = max_contacts_per_page
self.delivery_classes = self._reverse_delivery_class()

@staticmethod
def _pick_fields(data, keys):
Expand Down Expand Up @@ -91,6 +94,33 @@ def all_keys(self):
"""
raise NotImplementedError()

def _reverse_delivery_class(self):
res = defaultdict(list)
for cls, value in DELIVERY_CLASSES.iteritems():
res[value['field']].append(cls)
return res

@inlineCallbacks
def _get_contacts_by_query(self, query):
try:
[field, value] = query.split('=')
except ValueError:
raise CollectionUsageError(
"Query must be of the form 'field=value'")
valid_keys = self.delivery_classes.keys()
if field not in valid_keys:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could combine these two lines into if field not in self.delivery_classes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And just use self.delivery_classes.keys() in the error message (perhaps sorted?).

raise CollectionUsageError(
"Query field must be one of: %s" % valid_keys)

try:
contact = yield self.contact_store.contact_for_addr(
self.delivery_classes[field][0], value, create=False)
except ContactNotFoundError:
raise CollectionObjectNotFound(
'Contact with %s %s' % (field, value))

returnValue([contact_to_dict(contact)])

def stream(self, query):
"""
Return a :class:`PausingDeferredQueue` of the objects in the
Expand Down Expand Up @@ -137,7 +167,7 @@ def page(self, cursor, max_results, query):
to ``None`` if no limit was specified.
:param unicode query:
Search term requested through the API. Defaults to ``None`` if no
search term was requested.
search term was requested. Query must be of the form `field=value`.

:return:
(cursor, data). ``cursor`` is an opaque string that refers to the
Expand All @@ -146,7 +176,8 @@ def page(self, cursor, max_results, query):
:rtype: tuple
"""
if query is not None:
raise CollectionUsageError("query parameter not supported")
contacts = yield self._get_contacts_by_query(query)
returnValue((None, contacts))

max_results = max_results or float('inf')
max_results = min(max_results, self.max_contacts_per_page)
Expand Down
56 changes: 52 additions & 4 deletions go_contacts/tests/server_contacts_test_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -407,13 +407,61 @@ def test_page_bad_cursor(self):
u"Riak error, possible invalid cursor: u'bad-id'")

@inlineCallbacks
def test_page_query(self):
def test_page_invalid_query_format(self):
"""
If a query parameter is supplied, a CollectionUsageError should be
thrown, as querys are not yet supported.
If an invalid query format is supplied, a CollectionUsageError should
be thrown.
"""
api = self.mk_api()
code, data = yield self.request(api, 'GET', '/contacts/?query=foo')
self.assertEqual(code, 400)
self.assertEqual(data.get(u'status_code'), 400)
self.assertEqual(data.get(u'reason'), u'query parameter not supported')
self.assertEqual(
data.get(u'reason'), u"Query must be of the form 'field=value'")

@inlineCallbacks
def test_page_invalid_query_parameter(self):
"""
If an invalid query parameter is supplied, a CollectionUsageError
should be thrown.
"""
api = self.mk_api()
code, data = yield self.request(
api, 'GET', '/contacts/?query="foo=bar"')
self.assertEqual(code, 400)
self.assertEqual(data.get(u'status_code'), 400)
self.assertEqual(
data.get(u'reason'),
u"Query field must be one of: ['msisdn', 'wechat_id', 'gtalk_id',"
" 'twitter_handle', 'mxit_id']")

@inlineCallbacks
def test_page_with_query(self):
"""
If a valid query is supplied, the contact should be returned
"""
api = self.mk_api()
contact = yield self.create_contact(api, name=u'Bob', msisdn=u'+12345')
yield self.create_contact(api, name=u'Sue', msisdn=u'+54321')

code, data = yield self.request(
api, 'GET', '/contacts/?query=msisdn=%2B12345')
self.assertEqual(code, 200)
self.assertEqual(data.get(u'cursor'), None)
self.assertEqual(data.get('data'), [contact])

@inlineCallbacks
def test_page_with_query_no_contact_found(self):
"""
If no contact exists that fulfills the query, a ContactNotFoundError
should be thrown.
"""
api = self.mk_api()

code, data = yield self.request(
api, 'GET', '/contacts/?query=msisdn=bar')
self.assertEqual(code, 400)
self.assertEqual(data.get(u'status_code'), 400)
self.assertEqual(
data.get('reason'),
u"Object u'Contact with msisdn bar' not found.")