Skip to content

Commit

Permalink
Adds basic async client testing
Browse files Browse the repository at this point in the history
  • Loading branch information
bennylope committed Oct 25, 2014
1 parent 2a577da commit 073922d
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 11 deletions.
4 changes: 4 additions & 0 deletions requirements/py2.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
wheel==0.23.0
grequests==0.2.0
mock>=1.0.0
responses==0.3.0
File renamed without changes.
21 changes: 19 additions & 2 deletions smartystreets/async.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from .client import Client, validate_args
from .data import AddressCollection
from .exceptions import SmartyStreetsError, ERROR_CODES

try:
# Python 2
Expand Down Expand Up @@ -90,11 +91,27 @@ def post(self, endpoint, data, parallelism=5):
status_codes[response.status_code] = 1
else:
status_codes[response.status_code] += 1

if response.status_code == 200:
addresses[0:0] = AddressCollection(response.json()) # Fast list insertion

# If a request results in an exception this should be logged, but ignored unless *all*
# responses result in an exception
# If an auth error is raised, it's safe to say that this is
# going to affect every request, so raise the exception immediately..
elif response.status_code == 401:
raise ERROR_CODES[401]

# The return value or exception is simple if it is consistent.
if len(status_codes.keys()) == 1:
if 200 in status_codes:
return addresses, status_codes
else:
raise ERROR_CODES.get(status_codes.keys()[0], SmartyStreetsError)

# For any other mix not really sure of the best way to handle it. If it's a mix of 200
# and error codes, then returning the resultant addresses and status code dictionary
# seems pretty sensible. But if it's a mix of all error codes (could be a mix of payment
# error, input error, potentially server error) this will probably require careful
# checking in the code using this interface.
return addresses, status_codes

@validate_args
Expand Down
9 changes: 1 addition & 8 deletions smartystreets/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,9 @@
import requests

from .data import Address, AddressCollection
from .exceptions import (SmartyStreetsError, SmartyStreetsInputError, SmartyStreetsAuthError,
SmartyStreetsPaymentError, SmartyStreetsServerError)
from .exceptions import SmartyStreetsError, ERROR_CODES


ERROR_CODES = {
400: SmartyStreetsInputError,
401: SmartyStreetsAuthError,
422: SmartyStreetsPaymentError,
500: SmartyStreetsServerError,
}


def validate_args(f):
Expand Down
9 changes: 9 additions & 0 deletions smartystreets/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Exceptions for SmartyStreets requests.
"""


class SmartyStreetsError(Exception):
"""Unknown SmartyStreets error"""

Expand All @@ -23,3 +24,11 @@ class SmartyStreetsPaymentError(SmartyStreetsError):

class SmartyStreetsServerError(SmartyStreetsError):
"""HTTP 500 Internal server error. General service failure; retry request."""


ERROR_CODES = {
400: SmartyStreetsInputError,
401: SmartyStreetsAuthError,
422: SmartyStreetsPaymentError,
500: SmartyStreetsServerError,
}
79 changes: 79 additions & 0 deletions tests/test_async.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
test_smartystreets
----------------------------------
Tests for `smartystreets` module.
Note that for now async behavior is provided via grequests, which uses gevent, which
in packaged form is only supported on Python 2.7. As such this functionality is not
expected to work on Python 3 (for now) which means the tests here are skipped for
Python 3. This is a sad.
"""
import responses
import sys
import unittest
from mock import MagicMock

from smartystreets.data import Address, AddressCollection
from smartystreets.exceptions import (SmartyStreetsInputError, SmartyStreetsAuthError,
SmartyStreetsPaymentError, SmartyStreetsServerError)

PY_VERSION = int(sys.version[0])

try:
import grequests
except ImportError:
# This satisfies the named uses below without needing to import
# each and every time the AsyncClient is used.
from smartystreets import Client as AsyncClient
else:
from smartystreets.async import AsyncClient


@unittest.skipIf(PY_VERSION > 2, "No gevent support in Python 3")
class TestAsyncClient(unittest.TestCase):

def setUp(self):
self.client = AsyncClient(auth_id='blah', auth_token='blibbidy')

@responses.activate
def test_input_error(self):
responses.add(responses.POST, 'https://api.smartystreets.com/street-address',
body='', status=400,
content_type='application/json')
self.assertRaises(SmartyStreetsInputError, self.client.street_addresses, [{}, {}])

@responses.activate
def test_auth_error(self):
responses.add(responses.POST, 'https://api.smartystreets.com/street-address',
body='', status=401,
content_type='application/json')
self.assertRaises(SmartyStreetsAuthError, self.client.street_addresses, [{}, {}])

@responses.activate
def test_payment_error(self):
responses.add(responses.POST, 'https://api.smartystreets.com/street-address',
body='', status=422,
content_type='application/json')
self.assertRaises(SmartyStreetsPaymentError, self.client.street_addresses, [{}, {}])

@responses.activate
def test_server_error(self):
responses.add(responses.POST, 'https://api.smartystreets.com/street-address',
body='', status=500,
content_type='application/json')
self.assertRaises(SmartyStreetsServerError, self.client.street_addresses, [{}, {}])

@responses.activate
def test_addresses_response(self):
"""Ensure address return an AddressCollection"""
responses.add(responses.POST, 'https://api.smartystreets.com/street-address',
body='[{"street_address": "100 Main St"}, {"street_address": "200 Main St"}]',
status=200, content_type='application/json')
response = self.client.street_addresses([{"street": "100 Main st"},
{"street": "200 Main St"}])
self.assertIsInstance(response[0], AddressCollection)
self.assertEqual(2, len(response[0]))
6 changes: 5 additions & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,8 @@ setenv =
PYTHONPATH = {toxinidir}:{toxinidir}/smartystreets
commands = python setup.py test
deps =
-r{toxinidir}/requirements.txt
-r{toxinidir}/requirements/py3.txt

[testenv:py27]
deps =
-r{toxinidir}/requirements/py2.txt

0 comments on commit 073922d

Please sign in to comment.