def fake_response(link, content, **response_data): """A fake response that can be added to the mirror. """ redirects = response_data.pop("redirects", []) # Use the fake internet system to generate a response object. # This is more reliable than putting on together manually. data = {"stream": content} data.update(response_data) with internet(**{link.original_url: data}): session = TestSession() session.mount("http://", TestAdapter()) session.mount("https://", TestAdapter()) response = session.request("GET", link.original_url) # Additional attributes are expected. This is what the spider # does before passing a link to mirror.add(). Possibly we should # have less code duplication here with the actual spider code. parser_class = get_parser_for_mimetype(get_content_type(response)) if parser_class: response.parsed = parser_class(response.content, response.url, encoding=response.encoding) else: response.parsed = None response.links_parsed = HeaderLinkParser(response) response.redirects = redirects return response
def fake_response(link, content, **response_data): """A fake response that can be added to the mirror. """ redirects = response_data.pop('redirects', []) # Use the fake internet system to generate a response object. # This is more reliable than putting on together manually. data = {'stream': content} data.update(response_data) with internet(**{link.original_url: data}): session = TestSession() session.mount('http://', TestAdapter()) session.mount('https://', TestAdapter()) response = session.request('GET', link.original_url) # Additional attributes are expected. This is what the spider # does before passing a link to mirror.add(). Possibly we should # have less code duplication here with the actual spider code. parser_class = get_parser_for_mimetype(get_content_type(response)) if parser_class: response.parsed = parser_class(response.content, response.url, encoding=response.encoding) else: response.parsed = None response.links_parsed = HeaderLinkParser(response) response.redirects = redirects return response
class TestOptOutsApiClient(TestCase): def setUp(self): self.session = TestSession() self.client = OptOutsApiClient( auth_token="auth-token", api_url="http://example.com/api/v1/go", session=self.session) def response_ok(self, data): data.update({ u'status': { u'reason': u'OK', u'code': 200, }, }) return data def test_default_session(self): import requests client = OptOutsApiClient( auth_token="auth-token") self.assertTrue(isinstance(client.session, requests.Session)) def test_default_api_url(self): client = OptOutsApiClient( auth_token="auth-token") self.assertEqual(client.api_url, "https://go.vumi.org/api/v1/go") def check_request(self, request, method, data=None, headers=None): self.assertEqual(request.method, method) if headers is not None: for key, value in headers.items(): self.assertEqual(request.headers[key], value) if data is None: self.assertEqual(request.body, None) else: self.assertEqual(json.loads(request.body), data) def test_error_response(self): response = { u'status': { u'reason': u'Bad request', u'code': 400, }, } adapter = RecordingAdapter(json.dumps(response), status=400) self.session.mount( "http://example.com/api/v1/go/" "optouts/msisdn/%2b1234", adapter) self.assertRaises( HTTPError, self.client.get_optout, "msisdn", "+1234") self.check_request( adapter.request, 'GET', headers={"Authorization": u'Bearer auth-token'}) def test_non_json_error_response(self): response = "Not JSON" adapter = RecordingAdapter(json.dumps(response), status=503) self.session.mount( "http://example.com/api/v1/go/" "optouts/msisdn/%2b1234", adapter) self.assertRaises( HTTPError, self.client.get_optout, "msisdn", "+1234") def test_get_optout(self): opt_out = { u'created_at': u'2015-11-10 20:33:03.742409', u'message': None, u'user_account': u'fxxxeee', } response = self.response_ok({u'opt_out': opt_out}) adapter = RecordingAdapter(json.dumps(response)) self.session.mount( "http://example.com/api/v1/go/" "optouts/msisdn/%2b1234", adapter) result = self.client.get_optout("msisdn", "+1234") self.assertEqual(result, opt_out) self.check_request( adapter.request, 'GET', headers={"Authorization": u'Bearer auth-token'}) def test_get_optout_not_found(self): response = { u'status': { u'reason': u'Opt out not found', u'code': 404, }, } adapter = RecordingAdapter(json.dumps(response), status=404) self.session.mount( "http://example.com/api/v1/go/" "optouts/msisdn/%2b1234", adapter) result = self.client.get_optout("msisdn", "+1234") self.assertEqual(result, None) def test_set_optout(self): opt_out = { u'created_at': u'2015-11-10 20:33:03.742409', u'message': None, u'user_account': u'fxxxeee', } response = self.response_ok({u'opt_out': opt_out}) adapter = RecordingAdapter(json.dumps(response)) self.session.mount( "http://example.com/api/v1/go/" "optouts/msisdn/%2b1234", adapter) result = self.client.set_optout("msisdn", "+1234") self.assertEqual(result, opt_out) self.check_request( adapter.request, 'PUT', headers={"Authorization": u'Bearer auth-token'}) def test_delete_optout(self): opt_out = { u'created_at': u'2015-11-10 20:33:03.742409', u'message': None, u'user_account': u'fxxxeee', } response = self.response_ok({u'opt_out': opt_out}) adapter = RecordingAdapter(json.dumps(response)) self.session.mount( "http://example.com/api/v1/go/" "optouts/msisdn/%2b1234", adapter) result = self.client.delete_optout("msisdn", "+1234") self.assertEqual(result, opt_out) self.check_request( adapter.request, 'DELETE', headers={"Authorization": u'Bearer auth-token'}) def test_delete_optout_not_found(self): response = { u'status': { u'reason': u'Opt out not found', u'code': 404, }, } adapter = RecordingAdapter(json.dumps(response), status=404) self.session.mount( "http://example.com/api/v1/go/" "optouts/msisdn/%2b1234", adapter) result = self.client.delete_optout("msisdn", "+1234") self.assertEqual(result, None) def test_count(self): response = self.response_ok({ u'opt_out_count': 2, }) adapter = RecordingAdapter(json.dumps(response)) self.session.mount( "http://example.com/api/v1/go/" "optouts/count", adapter) result = self.client.count() self.assertEqual(result, 2) self.check_request( adapter.request, 'GET', headers={"Authorization": u'Bearer auth-token'})
class TestAccountApiClient(TestCase): API_URL = "http://example.com/go" AUTH_TOKEN = "auth_token" def setUp(self): self.account_backend = FakeAccountApi("go/", self.AUTH_TOKEN) self.session = TestSession() self.adapter = FakeAccountApiAdapter(self.account_backend) self.simulate_api_up() def simulate_api_down(self): self.session.mount(self.API_URL, TestAdapter("API is down", 500)) def simulate_api_up(self): self.session.mount(self.API_URL, self.adapter) def make_client(self, auth_token=AUTH_TOKEN): return AccountApiClient( auth_token, api_url=self.API_URL, session=self.session) def assert_http_error(self, expected_status, func, *args, **kw): try: func(*args, **kw) except HTTPError as err: self.assertEqual(err.response.status_code, expected_status) else: self.fail( "Expected HTTPError with status %s." % (expected_status,)) def assert_jsonrpc_exception(self, f, *args, **kw): try: f(*args, **kw) except Exception as err: self.assertTrue(isinstance(err, JsonRpcException)) self.assertTrue(isinstance(err.fault, unicode)) self.assertTrue(isinstance(err.fault_code, int)) self.assertTrue(isinstance(err.fault_string, unicode)) return err def test_assert_http_error(self): self.session.mount("http://bad.example.com/", TestAdapter("", 500)) def bad_req(): r = self.session.get("http://bad.example.com/") r.raise_for_status() # Fails when no exception is raised. self.assertRaises( self.failureException, self.assert_http_error, 404, lambda: None) # Fails when an HTTPError with the wrong status code is raised. self.assertRaises( self.failureException, self.assert_http_error, 404, bad_req) # Passes when an HTTPError with the expected status code is raised. self.assert_http_error(500, bad_req) # Non-HTTPError exceptions aren't caught. def raise_error(): raise ValueError() self.assertRaises(ValueError, self.assert_http_error, 404, raise_error) def test_default_session(self): client = AccountApiClient(self.AUTH_TOKEN) self.assertTrue(isinstance(client.session, Session)) def test_default_api_url(self): client = AccountApiClient(self.AUTH_TOKEN) self.assertEqual( client.api_url, "https://go.vumi.org/api/v1/go") def test_auth_failure(self): client = self.make_client(auth_token="bogus_token") self.assert_http_error(403, client.campaigns) def test_jsonrpc_error_handling(self): client = self.make_client() self.account_backend.add_error_response( "campaigns", [], fault="Fault", fault_code=8002, fault_string="Meep") err = self.assert_jsonrpc_exception(client.campaigns) self.assertEqual(err.fault, "Fault") self.assertEqual(err.fault_code, 8002) self.assertEqual(err.fault_string, "Meep") def test_campaigns(self): client = self.make_client() self.account_backend.add_success_response( "campaigns", [], fixtures.campaigns) self.assertEqual(client.campaigns(), fixtures.campaigns) def test_conversations(self): client = self.make_client() self.account_backend.add_success_response( "conversations", ["campaign-1"], fixtures.conversations) self.assertEqual( client.conversations("campaign-1"), fixtures.conversations) def test_channels(self): client = self.make_client() self.account_backend.add_success_response( "channels", ["campaign-1"], fixtures.channels) self.assertEqual( client.channels("campaign-1"), fixtures.channels) def test_routers(self): client = self.make_client() self.account_backend.add_success_response( "routers", ["campaign-1"], fixtures.routers) self.assertEqual( client.routers("campaign-1"), fixtures.routers) def test_routing_entries(self): client = self.make_client() self.account_backend.add_success_response( "routing_entries", ["campaign-1"], fixtures.routing_entries) self.assertEqual( client.routing_entries("campaign-1"), fixtures.routing_entries) def test_routing_table(self): client = self.make_client() self.account_backend.add_success_response( "routing_table", ["campaign-1"], fixtures.routing_table) self.assertEqual( client.routing_table("campaign-1"), fixtures.routing_table) def test_update_routing_tabel(self): client = self.make_client() self.account_backend.add_success_response( "update_routing_table", ["campaign-1", fixtures.routing_table], None) self.assertEqual( client.update_routing_table("campaign-1", fixtures.routing_table), None)
class ApiHelper(object): def __init__(self, test_case, api_url='http://example.com/api/v1'): self.test_case = test_case self.api_url = api_url self.session = TestSession() self._patches = [] def tearDown(self): for obj, name, orig_api in reversed(self._patches): setattr(obj, name, orig_api) def patch(self, obj, name, patched_obj): orig = getattr(obj, name) self._patches.append((obj, name, orig)) setattr(obj, name, patched_obj) def patch_api(self, obj, name): def patched_api(*args, **kw): kw['session'] = self.session kw['api_url'] = self.api_url return orig_api(*args, **kw) orig_api = getattr(obj, name) self.patch(obj, name, patched_api) def patch_api_method(self, obj, name, method, patched_method): def patched_api(*args, **kw): api = orig_api(*args, **kw) setattr(api, method, patched_method) return api orig_api = getattr(obj, name) self.patch(obj, name, patched_api) def check_response(self, adapter, method, data=None, headers=None): request = adapter.request auth_headers = adapter.expected_auth_headers self.test_case.assertEqual(request.method, method) if data is not None: self.test_case.assertEqual(json.loads(request.body), data) if headers is not None: for key, value in headers.items(): self.test_case.assertEqual(request.headers[key], value) if auth_headers is not None: for key, value in auth_headers.items(): self.test_case.assertEqual(request.headers[key], value) def add_send(self, account_key, conv_key, conv_token, data=None): adapter = RecordingAdapter(json.dumps(data)) adapter.set_basic_auth(account_key, conv_token) self.session.mount("%s/%s/messages.json" % (self.api_url, conv_key), adapter) return adapter def add_contacts(self, auth_token, start_cursor=None, contacts=(), cursor=None): page = { "data": contacts, "cursor": cursor, } adapter = RecordingAdapter(json.dumps(page)) adapter.set_bearer_auth(auth_token) params = "" if start_cursor is not None: params = "?" + urllib.urlencode({"cursor": start_cursor}) url = "%s/contacts/%s" % (self.api_url, params) self.session.mount(url, adapter) return adapter
class TestHttpApiSender(TestCase): def setUp(self): self.session = TestSession() self.sender = HttpApiSender( account_key="acc-key", conversation_key="conv-key", api_url="http://example.com/api/v1/go/http_api_nostream", conversation_token="conv-token", session=self.session) def test_default_session(self): import requests sender = HttpApiSender( account_key="acc-key", conversation_key="conv-key", conversation_token="conv-token") self.assertTrue(isinstance(sender.session, requests.Session)) def test_default_api_url(self): sender = HttpApiSender( account_key="acc-key", conversation_key="conv-key", conversation_token="conv-token") self.assertEqual(sender.api_url, "https://go.vumi.org/api/v1/go/http_api_nostream") def check_request(self, request, method, data=None, headers=None): self.assertEqual(request.method, method) if data is not None: self.assertEqual(json.loads(request.body), data) if headers is not None: for key, value in headers.items(): self.assertEqual(request.headers[key], value) def check_successful_send(self, send, expected_data): adapter = RecordingAdapter(json.dumps({"message_id": "id-1"})) self.session.mount( "http://example.com/api/v1/go/http_api_nostream/conv-key/" "messages.json", adapter) result = send() self.assertEqual(result, { "message_id": "id-1", }) self.check_request( adapter.request, 'PUT', data=expected_data, headers={"Authorization": u'Basic YWNjLWtleTpjb252LXRva2Vu'}) def test_send_text(self): self.check_successful_send( lambda: self.sender.send_text("to-addr-1", "Hello!"), { "content": "Hello!", "to_addr": "to-addr-1", }) def test_send_text_with_session_event(self): self.check_successful_send( lambda: self.sender.send_text( "to-addr-1", "Hello!", session_event="close"), { "content": "Hello!", "to_addr": "to-addr-1", "session_event": "close", }) def test_send_text_to_opted_out(self): """ UserOptedOutException raised for sending messages to opted out recipients """ self.session.mount( "http://example.com/api/v1/go/http_api_nostream/conv-key/" "messages.json", TestAdapter( json.dumps({ "success": False, "reason": "Recipient with msisdn to-addr-1 has opted out"} ), status=400)) try: self.sender.send_text('to-addr-1', "foo") except UserOptedOutException as e: self.assertEqual(e.to_addr, 'to-addr-1') self.assertEqual(e.message, 'foo') self.assertEqual( e.reason, 'Recipient with msisdn to-addr-1 has opted out') def test_send_text_to_other_http_error(self): """ HTTP errors should not be raised as UserOptedOutExceptions if they are not user opted out errors. """ self.session.mount( "http://example.com/api/v1/go/http_api_nostream/conv-key/" "messages.json", TestAdapter( json.dumps({ "success": False, "reason": "No unicorns were found" }), status=400)) try: self.sender.send_text('to-addr-1', 'foo') except HTTPError as e: self.assertEqual(e.response.status_code, 400) response = e.response.json() self.assertFalse(response['success']) self.assertEqual(response['reason'], "No unicorns were found") def test_send_text_to_other_http_error_not_json(self): """ HTTP errors that are not json encode should be raised without decoding errors """ self.session.mount( "http://example.com/api/v1/go/http_api_nostream/conv-key/" "messages.json", TestAdapter( "401 Client Error: Unauthorized", status=401)) try: self.sender.send_text('to-addr-1', 'foo') except HTTPError as e: self.assertEqual(e.response.status_code, 401) self.assertEqual(e.response.text, "401 Client Error: Unauthorized") def test_send_voice(self): self.check_successful_send( lambda: self.sender.send_voice("to-addr-1", "Hello!"), { "content": "Hello!", "to_addr": "to-addr-1", }) def test_send_voice_with_session_event(self): self.check_successful_send( lambda: self.sender.send_voice( "to-addr-1", "Hello!", session_event="close"), { "content": "Hello!", "to_addr": "to-addr-1", "session_event": "close", }) def test_send_voice_with_speech_url(self): self.check_successful_send( lambda: self.sender.send_voice( "to-addr-1", "Hello!", speech_url="http://example.com/voice.ogg"), { "content": "Hello!", "to_addr": "to-addr-1", "helper_metadata": { "voice": {"speech_url": "http://example.com/voice.ogg"}, }, }) def test_send_voice_with_wait_for(self): self.check_successful_send( lambda: self.sender.send_voice( "to-addr-1", "Hello!", wait_for="#"), { "content": "Hello!", "to_addr": "to-addr-1", "helper_metadata": { "voice": {"wait_for": "#"}, }, }) def test_fire_metric(self): adapter = RecordingAdapter( json.dumps({"success": True, "reason": "Yay"})) self.session.mount( "http://example.com/api/v1/go/http_api_nostream/conv-key/" "metrics.json", adapter) result = self.sender.fire_metric("metric-1", 5.1, agg="max") self.assertEqual(result, { "success": True, "reason": "Yay", }) self.check_request( adapter.request, 'PUT', data=[["metric-1", 5.1, "max"]], headers={"Authorization": u'Basic YWNjLWtleTpjb252LXRva2Vu'}) def test_fire_metric_default_agg(self): adapter = RecordingAdapter( json.dumps({"success": True, "reason": "Yay"})) self.session.mount( "http://example.com/api/v1/go/http_api_nostream/conv-key/" "metrics.json", adapter) result = self.sender.fire_metric("metric-1", 5.2) self.assertEqual(result, { "success": True, "reason": "Yay", }) self.check_request( adapter.request, 'PUT', data=[["metric-1", 5.2, "last"]], headers={"Authorization": u'Basic YWNjLWtleTpjb252LXRva2Vu'})
def make_session(pmr_info=None): session = TestSession() for url, adapter in endpoints: session.mount(url, adapter) return session
def testSession(self): s = TestSession() self.assertEqual([], list(s.adapters)) s.mount('http://git', TestAdapter(b'git')) s.mount('http://github', TestAdapter(b'github')) s.mount('http://github.com', TestAdapter(b'github.com')) s.mount('http://github.com/about/', TestAdapter(b'github.com/about')) order = [ 'http://github.com/about/', 'http://github.com', 'http://github', 'http://git', ] self.assertEqual(order, list(s.adapters)) s.mount('http://gittip', TestAdapter(b'gittip')) s.mount('http://gittip.com', TestAdapter(b'gittip.com')) s.mount('http://gittip.com/about/', TestAdapter(b'gittip.com/about')) order = [ 'http://github.com/about/', 'http://gittip.com/about/', 'http://github.com', 'http://gittip.com', 'http://github', 'http://gittip', 'http://git', ] self.assertEqual(order, list(s.adapters)) s2 = requests.Session() s2.adapters = {'http://': TestAdapter(b'http')} s2.mount('https://', TestAdapter(b'https')) self.assertTrue('http://' in s2.adapters) self.assertTrue('https://' in s2.adapters)
class TestSnappyApiSender(TestCase): def setUp(self): self.api_key = "dummy_key" self.betamax_record = "none" if os.environ.get("BESNAPPY_TEST_RECORD_REQUESTS"): if BESNAPPY_TEST_API_KEY: self.betamax_record = "all" self.api_key = BESNAPPY_TEST_API_KEY else: raise RuntimeError( "BESNAPPY_TEST_RECORD_REQUESTS is set, but" " BESNAPPY_TEST_API_KEY is not.") self.no_http_session = TestSession() self.betamax_session = Session() # We reset the Accept-Encoding header to avoid storing (opaque) gzip # response data. self.betamax_session.headers["Accept-Encoding"] = "" self.betamax = Betamax( self.betamax_session, cassette_library_dir=CASSETTE_LIBRARY_DIR) self.common_session = Session() # We reset the Accept-Encoding header to avoid storing (opaque) gzip # response data. self.common_session.headers["Accept-Encoding"] = "" self.betamax_common = Betamax( self.common_session, cassette_library_dir=CASSETTE_LIBRARY_DIR) self.betamax_placeholders = [] self.common_snappy = self._get_common_snappy() def tearDown(self): self.betamax.stop() self.betamax_common.stop() def add_betamax_placeholder(self, placeholder, replace): placeholder_dict = {"placeholder": placeholder, "replace": replace} if placeholder_dict not in self.betamax_placeholders: self.betamax_placeholders.append(placeholder_dict) def snappy_for_session(self, session, api_key=None, api_url=None): """ Build a ``SnappyApiSender`` instance using the provided session. """ if api_key is None: api_key = self.api_key return SnappyApiSender(api_key, api_url=api_url, session=session) def _snappy_for_betamax(self, betamax, cassette_name, api_key, api_url): """ Build a ``SnappyApiSender`` instance using the provided betamax object. """ if api_key is None: api_key = self.api_key auth_header_value = basic_auth_header_value(api_key) self.add_betamax_placeholder("$AUTH_HEADER$", auth_header_value) betamax.use_cassette( cassette_name, record=self.betamax_record, serialize_with="prettyjson", placeholders=self.betamax_placeholders, match_requests_on=["method", "uri"]) betamax.start() return self.snappy_for_session(betamax.session, api_key, api_url) def get_snappy(self, api_key=None, api_url=None): """ Build a ``SnappyApiSender`` instance using the test session. """ return self._snappy_for_betamax( self.betamax, self.id(), api_key, api_url) def _get_common_snappy(self, api_key=None, api_url=None): """ Build a ``SnappyApiSender`` instance using the common session. This uses a shared betamax cassette and is suitable for requests that set up the environment for the test to use rather than being part of the test logic. """ return self._snappy_for_betamax( self.betamax_common, "common", api_key, api_url) def test_api_request_url_construction_default_api_url(self): """ ``._api_request()`` constructs a URL by appending the ``endpoint`` to the ``api_url``. In this case, we use the default API URL. """ snappy = self.snappy_for_session(self.no_http_session) self.no_http_session.mount( "%s/flash" % (snappy.api_url,), TestAdapter("ok")) resp = snappy._api_request("GET", "flash") self.assertEqual(resp.content, "ok") def test_api_request_url_construction_custom_api_url(self): """ ``._api_request()`` constructs a URL by appending the ``endpoint`` to the ``api_url``. In this case, we provide a custom API URL. """ snappy = self.snappy_for_session( self.no_http_session, api_url="http://snappyapi.example.com/v1") self.no_http_session.mount( "http://snappyapi.example.com/v1/flash", TestAdapter("ok")) resp = snappy._api_request("GET", "flash") self.assertEqual(resp.content, "ok") #################################### # Tests for public API below here. # #################################### def get_account_id(self): """ Get an account_id for use in further tests. This uses the common betamax instead of the test-specific one. Because we don't have very much control over the account we're testing against, we always choose the first account in the list. """ accounts = self.common_snappy.get_accounts() return accounts[0]["id"] def get_mailbox_id(self): """ Get a mailbox_id for use in further tests. This uses the common betamax instead of the test-specific one. Because we don't have very much control over the account we're testing against, we always choose the first mailbox in first account in the list. """ account_id = self.get_account_id() mailboxes = self.common_snappy.get_mailboxes(account_id) return mailboxes[0]["id"] def get_staff_id(self): """ Get a staff_id for use in further tests. This uses the common betamax instead of the test-specific one. Because we don't have very much control over the account we're testing against, we always choose the first staff entry in first account in the list. """ account_id = self.get_account_id() staff = self.common_snappy.get_staff(account_id) return staff[0]["id"] def assert_looks_like_account_dict(self, obj): """ Assertion fails if the provided object doesn't look sufficiently like an account dict. TODO: Determine if this is good enough. """ self.assertTrue(isinstance(obj, dict), "Not a dict: %r" % (obj,)) account_fields = set([ "id", "organization", "domain", "plan_id", "active", "created_at", "updated_at", "custom_domain"]) missing_fields = account_fields - set(obj.keys()) self.assertEqual( missing_fields, set(), "Dict missing account fields: %s" % ( ", ".join(sorted(missing_fields)),)) def assert_looks_like_mailbox_dict(self, obj, account_id): """ Assertion fails if the provided object doesn't look sufficiently like a mailbox dict belonging to the specified account. TODO: Determine if this is good enough. """ self.assertTrue(isinstance(obj, dict), "Not a dict: %r" % (obj,)) mailbox_fields = set([ "id", "account_id", "type", "address", "display", "auto_responding", "auto_response", "active", "created_at", "updated_at", "custom_address", "theme", "local_part"]) missing_fields = mailbox_fields - set(obj.keys()) self.assertEqual( missing_fields, set(), "Dict missing mailbox fields: %s" % ( ", ".join(sorted(missing_fields)),)) self.assertEqual(obj["account_id"], account_id) def assert_looks_like_staff_dict(self, obj): """ Assertion fails if the provided object doesn't look sufficiently like a staff dict. TODO: Determine if this is good enough. """ self.assertTrue(isinstance(obj, dict), "Not a dict: %r" % (obj,)) staff_fields = set([ "id", "email", "sms_number", "first_name", "last_name", "photo", "culture", "notify", "created_at", "updated_at", "signature", "tour_played", "timezone", "notify_new", "news_read_at", "username"]) missing_fields = staff_fields - set(obj.keys()) self.assertEqual( missing_fields, set(), "Dict missing staff fields: %s" % ( ", ".join(sorted(missing_fields)),)) def test_get_accounts(self): """ ``.get_accounts()`` returns a list of accounts. Because we don't have very much control over the account we're testing against, we can only assert on the general structure. We always choose the first account in the list. """ snappy = self.get_snappy() accounts = snappy.get_accounts() self.assertTrue(isinstance(accounts, list)) self.assertTrue(len(accounts) >= 1) account = accounts[0] self.assert_looks_like_account_dict(account) # TODO: Make a call that requires an account identifier and assert that # it succeeds. def test_get_mailboxes(self): """ ``.get_mailboxes()`` returns a list of mailboxes for an account. Because we don't have very much control over the account we're testing against, we can only assert on the general structure. We always choose the first account in the list. """ account_id = self.get_account_id() snappy = self.get_snappy() mailboxes = snappy.get_mailboxes(account_id) self.assertTrue(isinstance(mailboxes, list)) self.assertTrue(len(mailboxes) >= 1) mailbox = mailboxes[0] self.assert_looks_like_mailbox_dict(mailbox, account_id) # TODO: Make a call that requires a mailbox identifier and assert that # it succeeds. def test_get_staff(self): """ ``.get_staff()`` returns a list of staffes for an account. Because we don't have very much control over the account we're testing against, we can only assert on the general structure. We always choose the first account in the list. """ account_id = self.get_account_id() snappy = self.get_snappy() staff = snappy.get_staff(account_id) self.assertTrue(isinstance(staff, list)) self.assertTrue(len(staff) >= 1) staff_member = staff[0] self.assert_looks_like_staff_dict(staff_member) # TODO: Make a call that requires a staff identifier and assert that # it succeeds. def test_create_note_new_ticket(self): """ Creating a note without a ticket identifier creates a new ticket. """ # NOTE: This placeholder needs to be set before we create the API # object betamax only loads its cassette data once upfront in # playback mode. subject_uuid = str(uuid.uuid4()) self.betamax_placeholders.append({ "placeholder": "$SUBJECT_UUID$", "replace": subject_uuid, }) mailbox_id = self.get_mailbox_id() snappy = self.get_snappy() ticket_subject = "Test subject %s" % (subject_uuid,) content = "What are the experiment protocols for subject %s?" % ( subject_uuid,) ticket_id = snappy.create_note( mailbox_id, ticket_subject, content, from_addr=[{ "name": "John Smith", "address": "*****@*****.**", }]) ticket_notes = snappy.get_ticket_notes(ticket_id) # A new ticket will only have one note. self.assertEqual( len(ticket_notes), 1, "Expected exactly 1 note, got %s: %r" % ( len(ticket_notes), ticket_notes)) [ticket_note] = ticket_notes # The note content should match the content we sent. self.assertEqual(strip_note_content(ticket_note["content"]), content) def test_create_note_new_private_from_staff(self): """ Creating a note without a ticket identifier creates a new ticket. """ # NOTE: This placeholder needs to be set before we create the API # object betamax only loads its cassette data once upfront in # playback mode. subject_uuid = str(uuid.uuid4()) self.betamax_placeholders.append({ "placeholder": "$SUBJECT_UUID$", "replace": subject_uuid, }) mailbox_id = self.get_mailbox_id() staff_id = self.get_staff_id() snappy = self.get_snappy() ticket_subject = "Private subject %s" % (subject_uuid,) content = "Has %s accepted the privacy policy?" % (subject_uuid,) addr = { "name": "John Smith", "address": "*****@*****.**", } ticket_id = snappy.create_note( mailbox_id, ticket_subject, content, to_addr=[addr], staff_id=staff_id, scope="private") ticket_notes = snappy.get_ticket_notes(ticket_id) # A new ticket will only have one note. self.assertEqual( len(ticket_notes), 1, "Expected exactly 1 note, got %s: %r" % ( len(ticket_notes), ticket_notes)) [ticket_note] = ticket_notes # The note content should match the content we sent. self.assertEqual(strip_note_content(ticket_note["content"]), content) def test_create_note_existing_ticket(self): """ Creating a note with a ticket identifier adds a note to the ticket. """ # NOTE: This placeholder needs to be set before we create the API # object betamax only loads its cassette data once upfront in # playback mode. subject_uuid = str(uuid.uuid4()) self.betamax_placeholders.append({ "placeholder": "$SUBJECT_UUID$", "replace": subject_uuid, }) mailbox_id = self.get_mailbox_id() snappy = self.get_snappy() # Create ticket ticket_subject = "Test subject %s" % (subject_uuid,) content = "What are the experiment protocols for subject %s?" % ( subject_uuid,) ticket_id = snappy.create_note( mailbox_id, ticket_subject, content, from_addr=[{ "name": "John Smith", "address": "*****@*****.**", }]) # Add a note to existing ticket. content_2 = "Do we need more goop for %s?" % (subject_uuid,) ticket_id_2 = snappy.create_note( mailbox_id, ticket_subject, content_2, ticket_id=ticket_id, from_addr=[{ "name": "John Smith", "address": "*****@*****.**", }]) # The ticket identifier should be the same. self.assertEqual(ticket_id, ticket_id_2) ticket_notes = snappy.get_ticket_notes(ticket_id) self.assertEqual( len(ticket_notes), 2, "Expected exactly 2 notes, got %s: %r" % ( len(ticket_notes), ticket_notes)) # NOTE: We have observed that the notes are ordered from newest to # oldest, but this is not documented. The note identifier appears # to be an increasing integer, but this is also not documented. # The timestamp lacks sufficient resolution to order the notes. # For now, we assume the observed ordering is consistent. [note_2, note_1] = ticket_notes # The note content should match the content we sent. self.assertEqual(strip_note_content(note_1["content"]), content) self.assertEqual(strip_note_content(note_2["content"]), content_2)
class TestMetricApiClient(TestCase): def setUp(self): self.session = TestSession() self.client = MetricsApiClient(auth_token="auth-token", api_url="http://example.com/api/v1/go", session=self.session) def test_default_session(self): import requests client = MetricsApiClient(auth_token="auth-token") self.assertTrue(isinstance(client.session, requests.Session)) def test_default_api_url(self): client = MetricsApiClient(auth_token="auth-token") self.assertEqual(client.api_url, "https://go.vumi.org/api/v1/go") def check_request(self, request, method, params=None, data=None, headers=None): self.assertEqual(request.method, method) if params is not None: url = urlparse.urlparse(request.url) qs = urlparse.parse_qsl(url.query) self.assertEqual(dict(qs), params) if headers is not None: for key, value in headers.items(): self.assertEqual(request.headers[key], value) if data is None: self.assertEqual(request.body, None) else: self.assertEqual(json.loads(request.body), data) def test_get_metric(self): response = { u'stores.store_name.metric_name.agg': [{ u'x': 1413936000000, u'y': 88916.0 }, { u'x': 1414022400000, u'y': 91339.0 }, { u'x': 1414108800000, u'y': 92490.0 }, { u'x': 1414195200000, u'y': 92655.0 }, { u'x': 1414281600000, u'y': 92786.0 }] } adapter = RecordingAdapter(json.dumps(response)) self.session.mount("http://example.com/api/v1/go/" "metrics/", adapter) result = self.client.get_metric("stores.store_name.metric_name.agg", "-30d", "1d", "omit") self.assertEqual(result, response) self.check_request(adapter.request, 'GET', params={ "m": "stores.store_name.metric_name.agg", "interval": "1d", "from": "-30d", "nulls": "omit" }, headers={"Authorization": u'Bearer auth-token'}) def test_fire(self): response = [{ 'name': 'foo.last', 'value': 3.1415, 'aggregator': 'last', }] adapter = RecordingAdapter(json.dumps(response)) self.session.mount("http://example.com/api/v1/go/" "metrics/", adapter) result = self.client.fire({ "foo.last": 3.1415, }) self.assertEqual(result, response) self.check_request(adapter.request, 'POST', data={"foo.last": 3.1415}, headers={"Authorization": u'Bearer auth-token'})
class TestContentStoreApiClient(TestCase): API_URL = "http://example.com/contentstore" AUTH_TOKEN = "auth_token" def setUp(self): self.messageset_data = {} self.schedule_data = {} self.message_data = {} self.binary_content_data = {} self.contentstore_backend = FakeContentStoreApi( "contentstore/", self.AUTH_TOKEN, messageset_data=self.messageset_data, schedule_data=self.schedule_data, message_data=self.message_data, binary_content_data=self.binary_content_data) self.session = TestSession() adapter = FakeContentStoreApiAdapter(self.contentstore_backend) self.session.mount(self.API_URL, adapter) self.client = self.make_client() def make_client(self, auth_token=AUTH_TOKEN): return ContentStoreApiClient( auth_token, api_url=self.API_URL, session=self.session) def make_existing_messageset(self, messageset_data): existing_messageset = make_messageset_dict(messageset_data) self.messageset_data[existing_messageset[u"id"]] = existing_messageset return existing_messageset def make_existing_message(self, message_data): existing_message = make_message_dict(message_data) self.message_data[existing_message[u"id"]] = existing_message return existing_message def make_existing_schedule(self, schedule_data): existing_schedule = make_schedule_dict(schedule_data) self.schedule_data[existing_schedule[u"id"]] = existing_schedule return existing_schedule def assert_messageset_status(self, messageset_id, exists=True): exists_status = (messageset_id in self.messageset_data) self.assertEqual(exists_status, exists) def assert_http_error(self, expected_status, func, *args, **kw): try: func(*args, **kw) except HTTPError as err: self.assertEqual(err.response.status_code, expected_status) else: self.fail( "Expected HTTPError with status %s." % (expected_status,)) def test_assert_http_error(self): self.session.mount("http://bad.example.com/", TestAdapter("", 500)) def bad_req(): r = self.session.get("http://bad.example.com/") r.raise_for_status() # Fails when no exception is raised. self.assertRaises( self.failureException, self.assert_http_error, 404, lambda: None) # Fails when an HTTPError with the wrong status code is raised. self.assertRaises( self.failureException, self.assert_http_error, 404, bad_req) # Passes when an HTTPError with the expected status code is raised. self.assert_http_error(500, bad_req) # Non-HTTPError exceptions aren't caught. def raise_error(): raise ValueError() self.assertRaises(ValueError, self.assert_http_error, 404, raise_error) def test_default_session(self): import requests contentstore = ContentStoreApiClient(self.AUTH_TOKEN) self.assertTrue(isinstance(contentstore.session, requests.Session)) def test_default_api_url(self): contentstore = ContentStoreApiClient(self.AUTH_TOKEN) self.assertEqual( contentstore.api_url, "http://testserver/contentstore") def test_auth_failure(self): contentstore = self.make_client(auth_token="bogus_token") self.assert_http_error(403, contentstore.get_messagesets) def test_get_messageset(self): expected_messageset = self.make_existing_messageset({ u"short_name": u"Full Set", u"notes": u"A full set of messages.", u"default_schedule": 1 }) [messageset] = list(self.client.get_messagesets()) self.assertEqual(messageset, expected_messageset) def test_create_messageset(self): new_messageset = self.client.create_messageset({ u"short_name": u"Full Set1", u"notes": u"A full and new set of messages.", u"default_schedule": 1 }) [messageset] = list(self.client.get_messagesets()) self.assertEqual( messageset["short_name"], new_messageset["short_name"]) self.assertEqual( messageset["notes"], new_messageset["notes"]) self.assertEqual( messageset["default_schedule"], new_messageset["default_schedule"]) def test_delete_messageset(self): new_messageset = self.make_existing_messageset({ u"short_name": u"Full Set", u"notes": u"A full set of messages that will go.", u"default_schedule": 1 }) [messageset] = list(self.client.get_messagesets()) self.assertEqual(messageset, new_messageset) self.client.delete_messageset(new_messageset["id"]) messageset = list(self.client.get_messagesets()) self.assertEqual(messageset, []) def test_get_message(self): expected_message = self.make_existing_message({ "messageset": 1, "sequence_number": 2, "lang": "afr_ZA", "text_content": "Message two" }) [message] = list(self.client.get_messages()) self.assertEqual(message, expected_message) def test_get_message_content(self): expected_message = self.make_existing_message({ "messageset": 1, "sequence_number": 2, "lang": "afr_ZA", "text_content": "Message two" }) message = self.client.get_message_content(expected_message[u"id"]) self.assertEqual(message, expected_message) def test_get_messageset_messages(self): new_messageset = self.client.create_messageset({ u"short_name": u"Full Set1", u"notes": u"A full and new set of messages.", u"default_schedule": 1 }) messageset_id = new_messageset["id"] expected_message = self.make_existing_message({ "messageset": messageset_id, "sequence_number": 2, "lang": "afr_ZA", "text_content": "Message two" }) expected_message2 = self.make_existing_message({ "messageset": messageset_id, "sequence_number": 1, "lang": "afr_ZA", "text_content": "Message one" }) messageset_messages = self.client.get_messageset_messages( messageset_id) self.assertEqual(len(messageset_messages["messages"]), 2) # should be sorted by sequence_number self.assertEqual(messageset_messages["messages"][0]["id"], expected_message2["id"]) self.assertEqual(messageset_messages["messages"][1]["id"], expected_message["id"]) def test_create_message(self): new_message = self.client.create_message({ "messageset": 1, "sequence_number": 2, "lang": "afr_ZA", "text_content": "Message two" }) [message] = list(self.client.get_messages()) self.assertEqual(message["text_content"], new_message["text_content"]) self.assertEqual(message["sequence_number"], new_message["sequence_number"]) self.assertEqual(message["messageset"], new_message["messageset"]) self.assertEqual(message["lang"], new_message["lang"]) def test_get_schedule(self): expected_schedule = self.make_existing_schedule({ "minute": "1", "hour": "2", "day_of_week": "3", "day_of_month": "4", "month_of_year": "5", }) [schedule] = list(self.client.get_schedules()) self.assertEqual(schedule, expected_schedule) def test_create_schedule(self): new_schedule = self.client.create_schedule({ "minute": "1", "hour": "2", "day_of_week": "3", "day_of_month": "4", "month_of_year": "5", }) [schedule] = list(self.client.get_schedules()) self.assertEqual(schedule["minute"], new_schedule["minute"]) self.assertEqual(schedule["hour"], new_schedule["hour"]) self.assertEqual(schedule["day_of_week"], new_schedule["day_of_week"]) self.assertEqual(schedule["day_of_month"], new_schedule["day_of_month"]) self.assertEqual(schedule["month_of_year"], new_schedule["month_of_year"])
class TestOptOutsApiClient(TestCase): def setUp(self): self.session = TestSession() self.client = OptOutsApiClient(auth_token="auth-token", api_url="http://example.com/api/v1/go", session=self.session) def response_ok(self, data): data.update({ u'status': { u'reason': u'OK', u'code': 200, }, }) return data def test_default_session(self): import requests client = OptOutsApiClient(auth_token="auth-token") self.assertTrue(isinstance(client.session, requests.Session)) def test_default_api_url(self): client = OptOutsApiClient(auth_token="auth-token") self.assertEqual(client.api_url, "https://go.vumi.org/api/v1/go") def check_request(self, request, method, data=None, headers=None): self.assertEqual(request.method, method) if headers is not None: for key, value in headers.items(): self.assertEqual(request.headers[key], value) if data is None: self.assertEqual(request.body, None) else: self.assertEqual(json.loads(request.body), data) def test_error_response(self): response = { u'status': { u'reason': u'Bad request', u'code': 400, }, } adapter = RecordingAdapter(json.dumps(response), status=400) self.session.mount( "http://example.com/api/v1/go/" "optouts/msisdn/%2b1234", adapter) self.assertRaises(HTTPError, self.client.get_optout, "msisdn", "+1234") self.check_request(adapter.request, 'GET', headers={"Authorization": u'Bearer auth-token'}) def test_non_json_error_response(self): response = "Not JSON" adapter = RecordingAdapter(json.dumps(response), status=503) self.session.mount( "http://example.com/api/v1/go/" "optouts/msisdn/%2b1234", adapter) self.assertRaises(HTTPError, self.client.get_optout, "msisdn", "+1234") def test_get_optout(self): opt_out = { u'created_at': u'2015-11-10 20:33:03.742409', u'message': None, u'user_account': u'fxxxeee', } response = self.response_ok({u'opt_out': opt_out}) adapter = RecordingAdapter(json.dumps(response)) self.session.mount( "http://example.com/api/v1/go/" "optouts/msisdn/%2b1234", adapter) result = self.client.get_optout("msisdn", "+1234") self.assertEqual(result, opt_out) self.check_request(adapter.request, 'GET', headers={"Authorization": u'Bearer auth-token'}) def test_get_optout_not_found(self): response = { u'status': { u'reason': u'Opt out not found', u'code': 404, }, } adapter = RecordingAdapter(json.dumps(response), status=404) self.session.mount( "http://example.com/api/v1/go/" "optouts/msisdn/%2b1234", adapter) result = self.client.get_optout("msisdn", "+1234") self.assertEqual(result, None) def test_set_optout(self): opt_out = { u'created_at': u'2015-11-10 20:33:03.742409', u'message': None, u'user_account': u'fxxxeee', } response = self.response_ok({u'opt_out': opt_out}) adapter = RecordingAdapter(json.dumps(response)) self.session.mount( "http://example.com/api/v1/go/" "optouts/msisdn/%2b1234", adapter) result = self.client.set_optout("msisdn", "+1234") self.assertEqual(result, opt_out) self.check_request(adapter.request, 'PUT', headers={"Authorization": u'Bearer auth-token'}) def test_delete_optout(self): opt_out = { u'created_at': u'2015-11-10 20:33:03.742409', u'message': None, u'user_account': u'fxxxeee', } response = self.response_ok({u'opt_out': opt_out}) adapter = RecordingAdapter(json.dumps(response)) self.session.mount( "http://example.com/api/v1/go/" "optouts/msisdn/%2b1234", adapter) result = self.client.delete_optout("msisdn", "+1234") self.assertEqual(result, opt_out) self.check_request(adapter.request, 'DELETE', headers={"Authorization": u'Bearer auth-token'}) def test_delete_optout_not_found(self): response = { u'status': { u'reason': u'Opt out not found', u'code': 404, }, } adapter = RecordingAdapter(json.dumps(response), status=404) self.session.mount( "http://example.com/api/v1/go/" "optouts/msisdn/%2b1234", adapter) result = self.client.delete_optout("msisdn", "+1234") self.assertEqual(result, None) def test_count(self): response = self.response_ok({ u'opt_out_count': 2, }) adapter = RecordingAdapter(json.dumps(response)) self.session.mount("http://example.com/api/v1/go/" "optouts/count", adapter) result = self.client.count() self.assertEqual(result, 2) self.check_request(adapter.request, 'GET', headers={"Authorization": u'Bearer auth-token'})
class TestContactsApiClient(TestCase): API_URL = "http://example.com/go" AUTH_TOKEN = "auth_token" MAX_CONTACTS_PER_PAGE = 10 def setUp(self): self.contacts_data = {} self.groups_data = {} self.contacts_backend = FakeContactsApi( "go/", self.AUTH_TOKEN, self.contacts_data, self.groups_data, contacts_limit=self.MAX_CONTACTS_PER_PAGE) self.session = TestSession() self.adapter = FakeContactsApiAdapter(self.contacts_backend) self.simulate_api_up() def simulate_api_down(self): self.session.mount(self.API_URL, TestAdapter("API is down", 500)) def simulate_api_up(self): self.session.mount(self.API_URL, self.adapter) def make_client(self, auth_token=AUTH_TOKEN): return ContactsApiClient(auth_token, api_url=self.API_URL, session=self.session) def make_existing_contact(self, contact_data): existing_contact = make_contact_dict(contact_data) self.contacts_data[existing_contact[u"key"]] = existing_contact return existing_contact def make_existing_group(self, group_data): existing_group = make_group_dict(group_data) self.groups_data[existing_group[u'key']] = existing_group return existing_group def make_n_contacts(self, n, groups=None): contacts = [] for i in range(n): data = { u"msisdn": u"+155564%d" % (i, ), u"name": u"Arthur", u"surname": u"of Camelot", } if groups is not None: data["groups"] = groups contacts.append(self.make_existing_contact(data)) return contacts def assert_contacts_equal(self, contacts_a, contacts_b): contacts_a.sort(key=lambda d: d['msisdn']) contacts_b.sort(key=lambda d: d['msisdn']) self.assertEqual(contacts_a, contacts_b) def assert_contact_status(self, contact_key, exists=True): exists_status = (contact_key in self.contacts_data) self.assertEqual(exists_status, exists) def assert_group_status(self, group_key, exists=True): exists_status = (group_key in self.groups_data) self.assertEqual(exists_status, exists) def assert_http_error(self, expected_status, func, *args, **kw): try: func(*args, **kw) except HTTPError as err: self.assertEqual(err.response.status_code, expected_status) else: self.fail("Expected HTTPError with status %s." % (expected_status, )) def assert_paged_exception(self, f, *args, **kw): try: f(*args, **kw) except Exception as err: self.assertTrue(isinstance(err, PagedException)) self.assertTrue(isinstance(err.cursor, unicode)) self.assertTrue(isinstance(err.error, Exception)) return err def test_assert_http_error(self): self.session.mount("http://bad.example.com/", TestAdapter("", 500)) def bad_req(): r = self.session.get("http://bad.example.com/") r.raise_for_status() # Fails when no exception is raised. self.assertRaises(self.failureException, self.assert_http_error, 404, lambda: None) # Fails when an HTTPError with the wrong status code is raised. self.assertRaises(self.failureException, self.assert_http_error, 404, bad_req) # Passes when an HTTPError with the expected status code is raised. self.assert_http_error(500, bad_req) # Non-HTTPError exceptions aren't caught. def raise_error(): raise ValueError() self.assertRaises(ValueError, self.assert_http_error, 404, raise_error) def test_default_session(self): import requests contacts = ContactsApiClient(self.AUTH_TOKEN) self.assertTrue(isinstance(contacts.session, requests.Session)) def test_default_api_url(self): contacts = ContactsApiClient(self.AUTH_TOKEN) self.assertEqual(contacts.api_url, "https://go.vumi.org/api/v1/go") def test_auth_failure(self): contacts = self.make_client(auth_token="bogus_token") self.assert_http_error(403, contacts.get_contact, "foo") def test_contacts_single_page(self): [expected_contact] = self.make_n_contacts(1) contacts_api = self.make_client() [contact] = list(contacts_api.contacts()) self.assertEqual(contact, expected_contact) def test_contacts_no_results(self): contacts_api = self.make_client() contacts = list(contacts_api.contacts()) self.assertEqual(contacts, []) def test_contacts_multiple_pages(self): expected_contacts = self.make_n_contacts(self.MAX_CONTACTS_PER_PAGE + 1) contacts_api = self.make_client() contacts = list(contacts_api.contacts()) self.assert_contacts_equal(contacts, expected_contacts) def test_contacts_multiple_pages_with_cursor(self): expected_contacts = self.make_n_contacts(self.MAX_CONTACTS_PER_PAGE + 1) contacts_api = self.make_client() first_page = contacts_api._api_request("GET", "contacts", "") cursor = first_page['cursor'] contacts = list(contacts_api.contacts(start_cursor=cursor)) contacts.extend(first_page['data']) self.assert_contacts_equal(contacts, expected_contacts) def test_contacts_multiple_pages_with_failure(self): expected_contacts = self.make_n_contacts(self.MAX_CONTACTS_PER_PAGE + 1) contacts_api = self.make_client() it = contacts_api.contacts() contacts = [it.next() for _ in range(self.MAX_CONTACTS_PER_PAGE)] self.simulate_api_down() err = self.assert_paged_exception(it.next) self.simulate_api_up() [last_contact] = list(contacts_api.contacts(start_cursor=err.cursor)) self.assert_contacts_equal(contacts + [last_contact], expected_contacts) def test_create_contact(self): contacts = self.make_client() contact_data = { u"msisdn": u"+15556483", u"name": u"Arthur", u"surname": u"of Camelot", } contact = contacts.create_contact(contact_data) expected_contact = make_contact_dict(contact_data) # The key is generated for us. expected_contact[u"key"] = contact[u"key"] self.assertEqual(contact, expected_contact) self.assert_contact_status(contact[u"key"], exists=True) def test_create_contact_with_extras(self): contacts = self.make_client() contact_data = { u"msisdn": u"+15556483", u"name": u"Arthur", u"surname": u"of Camelot", u"extra": { u"quest": u"Grail", u"sidekick": u"Percy", }, } contact = contacts.create_contact(contact_data) expected_contact = make_contact_dict(contact_data) # The key is generated for us. expected_contact[u"key"] = contact[u"key"] self.assertEqual(contact, expected_contact) self.assert_contact_status(contact[u"key"], exists=True) def test_create_contact_with_key(self): contacts = self.make_client() contact_data = { u"key": u"foo", u"msisdn": u"+15556483", u"name": u"Arthur", u"surname": u"of Camelot", } self.assert_http_error(400, contacts.create_contact, contact_data) self.assert_contact_status(u"foo", exists=False) def test_get_contact(self): contacts = self.make_client() existing_contact = self.make_existing_contact({ u"msisdn": u"+15556483", u"name": u"Arthur", u"surname": u"of Camelot", }) contact = contacts.get_contact(existing_contact[u"key"]) self.assertEqual(contact, existing_contact) def test_get_contact_with_extras(self): contacts = self.make_client() existing_contact = self.make_existing_contact({ u"msisdn": u"+15556483", u"name": u"Arthur", u"surname": u"of Camelot", u"extra": { u"quest": u"Grail", u"sidekick": u"Percy", }, }) contact = contacts.get_contact(existing_contact[u"key"]) self.assertEqual(contact, existing_contact) def test_get_missing_contact(self): contacts = self.make_client() self.assert_http_error(404, contacts.get_contact, "foo") def test_get_contact_from_field(self): contacts = self.make_client() existing_contact = self.make_existing_contact({ u"msisdn": u"+15556483", u"name": u"Arthur", u"surname": u"of Camelot", }) contact = contacts.get_contact(msisdn='+15556483') self.assertEqual(contact, existing_contact) def test_get_contact_from_field_missing(self): contacts = self.make_client() self.make_existing_contact({ u"msisdn": u"+15556483", u"name": u"Arthur", u"surname": u"of Camelot", }) self.assert_http_error(400, contacts.get_contact, msisdn='+12345') def test_update_contact(self): contacts = self.make_client() existing_contact = self.make_existing_contact({ u"msisdn": u"+15556483", u"name": u"Arthur", u"surname": u"of Camelot", }) new_contact = existing_contact.copy() new_contact[u"surname"] = u"Pendragon" contact = contacts.update_contact(existing_contact[u"key"], {u"surname": u"Pendragon"}) self.assertEqual(contact, new_contact) def test_update_contact_with_extras(self): contacts = self.make_client() existing_contact = self.make_existing_contact({ u"msisdn": u"+15556483", u"name": u"Arthur", u"surname": u"of Camelot", u"extra": { u"quest": u"Grail", u"sidekick": u"Percy", }, }) new_contact = existing_contact.copy() new_contact[u"surname"] = u"Pendragon" new_contact[u"extra"] = { u"quest": u"lunch", u"knight": u"Lancelot", } contact = contacts.update_contact( existing_contact[u"key"], { u"surname": u"Pendragon", u"extra": { u"quest": u"lunch", u"knight": u"Lancelot", }, }) self.assertEqual(contact, new_contact) def test_update_missing_contact(self): contacts = self.make_client() self.assert_http_error(404, contacts.update_contact, "foo", {}) def test_delete_contact(self): contacts = self.make_client() existing_contact = self.make_existing_contact({ u"msisdn": u"+15556483", u"name": u"Arthur", u"surname": u"of Camelot", }) self.assert_contact_status(existing_contact[u"key"], exists=True) contact = contacts.delete_contact(existing_contact[u"key"]) self.assertEqual(contact, existing_contact) self.assert_contact_status(existing_contact[u"key"], exists=False) def test_delete_missing_contact(self): contacts = self.make_client() self.assert_http_error(404, contacts.delete_contact, "foo") def test_create_group(self): client = self.make_client() group_data = { u'name': u'Bob', } group = client.create_group(group_data) expected_group = make_group_dict(group_data) # The key is generated for us. expected_group[u'key'] = group[u'key'] self.assertEqual(group, expected_group) self.assert_group_status(group[u'key'], exists=True) def test_create_smart_group(self): client = self.make_client() group_data = { u'name': u'Bob', u'query': u'test-query', } group = client.create_group(group_data) expected_group = make_group_dict(group_data) # The key is generated for us expected_group[u'key'] = group[u'key'] self.assertEqual(group, expected_group) self.assert_group_status(group[u'key'], exists=True) def test_create_group_with_key(self): client = self.make_client() group_data = { u'key': u'foo', u'name': u'Bob', u'query': u'test-query', } self.assert_http_error(400, client.create_group, group_data) def test_get_group(self): client = self.make_client() existing_group = self.make_existing_group({ u'name': 'Bob', }) group = client.get_group(existing_group[u'key']) self.assertEqual(group, existing_group) def test_get_smart_group(self): client = self.make_client() existing_group = self.make_existing_group({ u'name': 'Bob', u'query': 'test-query', }) group = client.get_group(existing_group[u'key']) self.assertEqual(group, existing_group) def test_get_missing_group(self): client = self.make_client() self.assert_http_error(404, client.get_group, 'foo') def test_update_group(self): client = self.make_client() existing_group = self.make_existing_group({ u'name': u'Bob', }) new_group = existing_group.copy() new_group[u'name'] = u'Susan' group = client.update_group(existing_group[u'key'], {'name': 'Susan'}) self.assertEqual(existing_group, group) self.assertEqual(group, new_group) def test_update_smart_group(self): client = self.make_client() existing_group = self.make_existing_group({ u'name': u'Bob', u'query': u'test-query', }) new_group = existing_group.copy() new_group[u'query'] = u'another-query' group = client.update_group(existing_group[u'key'], {'query': 'another-query'}) self.assertEqual(existing_group, group) self.assertEqual(group, new_group) def test_update_missing_group(self): client = self.make_client() self.assert_http_error(404, client.update_group, 'foo', {}) def test_delete_group(self): client = self.make_client() existing_group = self.make_existing_group({u'name': u'Bob'}) self.assert_group_status(existing_group[u'key'], exists=True) group = client.delete_group(existing_group[u'key']) self.assertEqual(existing_group, group) self.assert_group_status(group[u'key'], exists=False) def test_delete_missing_group(self): client = self.make_client() self.assert_http_error(404, client.delete_group, 'foo') def test_group_contacts_multiple_pages_with_cursor(self): self.make_existing_group({ u'name': 'key', }) expected_contacts = self.make_n_contacts(self.MAX_CONTACTS_PER_PAGE + 1, groups=["key"]) client = self.make_client() first_page = client._api_request("GET", "groups/key", "contacts") cursor = first_page['cursor'] contacts = list( client.group_contacts(group_key="key", start_cursor=cursor)) contacts.extend(first_page['data']) contacts.sort(key=lambda d: d['msisdn']) expected_contacts.sort(key=lambda d: d['msisdn']) self.assertEqual(contacts, expected_contacts) def test_group_contacts_multiple_pages(self): self.make_existing_group({ u'name': 'key', }) self.make_existing_group({ u'name': 'diffkey', }) expected_contacts = self.make_n_contacts(self.MAX_CONTACTS_PER_PAGE + 1, groups=["key"]) self.make_existing_contact({ u"msisdn": u"+1234567", u"name": u"Nancy", u"surname": u"of Camelot", u"groups": ["diffkey"], }) client = self.make_client() contacts = list(client.group_contacts("key")) self.assert_contacts_equal(contacts, expected_contacts) def test_group_contacts_multiple_pages_with_failure(self): self.make_existing_group({ u'name': 'key', }) self.make_existing_group({ u'name': 'diffkey', }) expected_contacts = self.make_n_contacts(self.MAX_CONTACTS_PER_PAGE + 1, groups=["key"]) self.make_existing_contact({ u"msisdn": u"+1234567", u"name": u"Nancy", u"surname": u"of Camelot", u"groups": ["diffkey"], }) contacts_api = self.make_client() it = contacts_api.group_contacts("key") contacts = [it.next() for _ in range(self.MAX_CONTACTS_PER_PAGE)] self.simulate_api_down() err = self.assert_paged_exception(it.next) self.simulate_api_up() [last_contact ] = list(contacts_api.group_contacts("key", start_cursor=err.cursor)) self.assert_contacts_equal(contacts + [last_contact], expected_contacts) def test_group_contacts_none_found(self): self.make_existing_group({ u'name': 'key', }) self.make_existing_group({ u'name': 'diffkey', }) self.make_n_contacts(self.MAX_CONTACTS_PER_PAGE + 1, groups=["diffkey"]) client = self.make_client() contacts = list(client.group_contacts("key")) self.assert_contacts_equal(contacts, [])
class TestContentStoreApiClient(TestCase): API_URL = "http://example.com/contentstore" AUTH_TOKEN = "auth_token" def setUp(self): self.messageset_data = {} self.schedule_data = {} self.message_data = {} self.binary_content_data = {} self.contentstore_backend = FakeContentStoreApi( "contentstore/", self.AUTH_TOKEN, messageset_data=self.messageset_data, schedule_data=self.schedule_data, message_data=self.message_data, binary_content_data=self.binary_content_data) self.session = TestSession() adapter = FakeContentStoreApiAdapter(self.contentstore_backend) self.session.mount(self.API_URL, adapter) self.client = self.make_client() def make_client(self, auth_token=AUTH_TOKEN): return ContentStoreApiClient(auth_token, api_url=self.API_URL, session=self.session) def make_existing_messageset(self, messageset_data): existing_messageset = make_messageset_dict(messageset_data) self.messageset_data[existing_messageset[u"id"]] = existing_messageset return existing_messageset def make_existing_message(self, message_data): existing_message = make_message_dict(message_data) self.message_data[existing_message[u"id"]] = existing_message return existing_message def make_existing_schedule(self, schedule_data): existing_schedule = make_schedule_dict(schedule_data) self.schedule_data[existing_schedule[u"id"]] = existing_schedule return existing_schedule def assert_messageset_status(self, messageset_id, exists=True): exists_status = (messageset_id in self.messageset_data) self.assertEqual(exists_status, exists) def assert_http_error(self, expected_status, func, *args, **kw): try: func(*args, **kw) except HTTPError as err: self.assertEqual(err.response.status_code, expected_status) else: self.fail("Expected HTTPError with status %s." % (expected_status, )) def test_assert_http_error(self): self.session.mount("http://bad.example.com/", TestAdapter("", 500)) def bad_req(): r = self.session.get("http://bad.example.com/") r.raise_for_status() # Fails when no exception is raised. self.assertRaises(self.failureException, self.assert_http_error, 404, lambda: None) # Fails when an HTTPError with the wrong status code is raised. self.assertRaises(self.failureException, self.assert_http_error, 404, bad_req) # Passes when an HTTPError with the expected status code is raised. self.assert_http_error(500, bad_req) # Non-HTTPError exceptions aren't caught. def raise_error(): raise ValueError() self.assertRaises(ValueError, self.assert_http_error, 404, raise_error) def test_default_session(self): import requests contentstore = ContentStoreApiClient(self.AUTH_TOKEN) self.assertTrue(isinstance(contentstore.session, requests.Session)) def test_default_api_url(self): contentstore = ContentStoreApiClient(self.AUTH_TOKEN) self.assertEqual(contentstore.api_url, "http://testserver/contentstore") def test_auth_failure(self): contentstore = self.make_client(auth_token="bogus_token") self.assert_http_error(403, contentstore.get_messagesets) def test_get_messageset(self): expected_messageset = self.make_existing_messageset({ u"short_name": u"Full Set", u"notes": u"A full set of messages.", u"default_schedule": 1 }) [messageset] = list(self.client.get_messagesets()) self.assertEqual(messageset, expected_messageset) def test_create_messageset(self): new_messageset = self.client.create_messageset({ u"short_name": u"Full Set1", u"notes": u"A full and new set of messages.", u"default_schedule": 1 }) [messageset] = list(self.client.get_messagesets()) self.assertEqual(messageset["short_name"], new_messageset["short_name"]) self.assertEqual(messageset["notes"], new_messageset["notes"]) self.assertEqual(messageset["default_schedule"], new_messageset["default_schedule"]) def test_delete_messageset(self): new_messageset = self.make_existing_messageset({ u"short_name": u"Full Set", u"notes": u"A full set of messages that will go.", u"default_schedule": 1 }) [messageset] = list(self.client.get_messagesets()) self.assertEqual(messageset, new_messageset) self.client.delete_messageset(new_messageset["id"]) messageset = list(self.client.get_messagesets()) self.assertEqual(messageset, []) def test_get_message(self): expected_message = self.make_existing_message({ "messageset": 1, "sequence_number": 2, "lang": "afr_ZA", "text_content": "Message two" }) [message] = list(self.client.get_messages()) self.assertEqual(message, expected_message) def test_get_message_content(self): expected_message = self.make_existing_message({ "messageset": 1, "sequence_number": 2, "lang": "afr_ZA", "text_content": "Message two" }) message = self.client.get_message_content(expected_message[u"id"]) self.assertEqual(message, expected_message) def test_get_messageset_messages(self): new_messageset = self.client.create_messageset({ u"short_name": u"Full Set1", u"notes": u"A full and new set of messages.", u"default_schedule": 1 }) messageset_id = new_messageset["id"] expected_message = self.make_existing_message({ "messageset": messageset_id, "sequence_number": 2, "lang": "afr_ZA", "text_content": "Message two" }) expected_message2 = self.make_existing_message({ "messageset": messageset_id, "sequence_number": 1, "lang": "afr_ZA", "text_content": "Message one" }) messageset_messages = self.client.get_messageset_messages( messageset_id) self.assertEqual(len(messageset_messages["messages"]), 2) # should be sorted by sequence_number self.assertEqual(messageset_messages["messages"][0]["id"], expected_message2["id"]) self.assertEqual(messageset_messages["messages"][1]["id"], expected_message["id"]) def test_create_message(self): new_message = self.client.create_message({ "messageset": 1, "sequence_number": 2, "lang": "afr_ZA", "text_content": "Message two" }) [message] = list(self.client.get_messages()) self.assertEqual(message["text_content"], new_message["text_content"]) self.assertEqual(message["sequence_number"], new_message["sequence_number"]) self.assertEqual(message["messageset"], new_message["messageset"]) self.assertEqual(message["lang"], new_message["lang"]) def test_get_schedule(self): expected_schedule = self.make_existing_schedule({ "minute": "1", "hour": "2", "day_of_week": "3", "day_of_month": "4", "month_of_year": "5", }) [schedule] = list(self.client.get_schedules()) self.assertEqual(schedule, expected_schedule) def test_create_schedule(self): new_schedule = self.client.create_schedule({ "minute": "1", "hour": "2", "day_of_week": "3", "day_of_month": "4", "month_of_year": "5", }) [schedule] = list(self.client.get_schedules()) self.assertEqual(schedule["minute"], new_schedule["minute"]) self.assertEqual(schedule["hour"], new_schedule["hour"]) self.assertEqual(schedule["day_of_week"], new_schedule["day_of_week"]) self.assertEqual(schedule["day_of_month"], new_schedule["day_of_month"]) self.assertEqual(schedule["month_of_year"], new_schedule["month_of_year"])
class TestAccountApiClient(TestCase): API_URL = "http://example.com/go" AUTH_TOKEN = "auth_token" def setUp(self): self.account_backend = FakeAccountApi("go/", self.AUTH_TOKEN) self.session = TestSession() self.adapter = FakeAccountApiAdapter(self.account_backend) self.simulate_api_up() def simulate_api_down(self): self.session.mount(self.API_URL, TestAdapter("API is down", 500)) def simulate_api_up(self): self.session.mount(self.API_URL, self.adapter) def make_client(self, auth_token=AUTH_TOKEN): return AccountApiClient(auth_token, api_url=self.API_URL, session=self.session) def assert_http_error(self, expected_status, func, *args, **kw): try: func(*args, **kw) except HTTPError as err: self.assertEqual(err.response.status_code, expected_status) else: self.fail("Expected HTTPError with status %s." % (expected_status, )) def assert_jsonrpc_exception(self, f, *args, **kw): try: f(*args, **kw) except Exception as err: self.assertTrue(isinstance(err, JsonRpcException)) self.assertTrue(isinstance(err.fault, unicode)) self.assertTrue(isinstance(err.fault_code, int)) self.assertTrue(isinstance(err.fault_string, unicode)) return err def test_assert_http_error(self): self.session.mount("http://bad.example.com/", TestAdapter("", 500)) def bad_req(): r = self.session.get("http://bad.example.com/") r.raise_for_status() # Fails when no exception is raised. self.assertRaises(self.failureException, self.assert_http_error, 404, lambda: None) # Fails when an HTTPError with the wrong status code is raised. self.assertRaises(self.failureException, self.assert_http_error, 404, bad_req) # Passes when an HTTPError with the expected status code is raised. self.assert_http_error(500, bad_req) # Non-HTTPError exceptions aren't caught. def raise_error(): raise ValueError() self.assertRaises(ValueError, self.assert_http_error, 404, raise_error) def test_default_session(self): client = AccountApiClient(self.AUTH_TOKEN) self.assertTrue(isinstance(client.session, Session)) def test_default_api_url(self): client = AccountApiClient(self.AUTH_TOKEN) self.assertEqual(client.api_url, "https://go.vumi.org/api/v1/go") def test_auth_failure(self): client = self.make_client(auth_token="bogus_token") self.assert_http_error(403, client.campaigns) def test_jsonrpc_error_handling(self): client = self.make_client() self.account_backend.add_error_response("campaigns", [], fault="Fault", fault_code=8002, fault_string="Meep") err = self.assert_jsonrpc_exception(client.campaigns) self.assertEqual(err.fault, "Fault") self.assertEqual(err.fault_code, 8002) self.assertEqual(err.fault_string, "Meep") def test_campaigns(self): client = self.make_client() self.account_backend.add_success_response("campaigns", [], fixtures.campaigns) self.assertEqual(client.campaigns(), fixtures.campaigns) def test_conversations(self): client = self.make_client() self.account_backend.add_success_response("conversations", ["campaign-1"], fixtures.conversations) self.assertEqual(client.conversations("campaign-1"), fixtures.conversations) def test_channels(self): client = self.make_client() self.account_backend.add_success_response("channels", ["campaign-1"], fixtures.channels) self.assertEqual(client.channels("campaign-1"), fixtures.channels) def test_routers(self): client = self.make_client() self.account_backend.add_success_response("routers", ["campaign-1"], fixtures.routers) self.assertEqual(client.routers("campaign-1"), fixtures.routers) def test_routing_entries(self): client = self.make_client() self.account_backend.add_success_response("routing_entries", ["campaign-1"], fixtures.routing_entries) self.assertEqual(client.routing_entries("campaign-1"), fixtures.routing_entries) def test_routing_table(self): client = self.make_client() self.account_backend.add_success_response("routing_table", ["campaign-1"], fixtures.routing_table) self.assertEqual(client.routing_table("campaign-1"), fixtures.routing_table) def test_update_routing_tabel(self): client = self.make_client() self.account_backend.add_success_response( "update_routing_table", ["campaign-1", fixtures.routing_table], None) self.assertEqual( client.update_routing_table("campaign-1", fixtures.routing_table), None)
class TestMetricApiClient(TestCase): def setUp(self): self.session = TestSession() self.client = MetricsApiClient( auth_token="auth-token", api_url="http://example.com/api/v1/go", session=self.session) def test_default_session(self): import requests client = MetricsApiClient( auth_token="auth-token") self.assertTrue(isinstance(client.session, requests.Session)) def test_default_api_url(self): client = MetricsApiClient( auth_token="auth-token") self.assertEqual(client.api_url, "https://go.vumi.org/api/v1/go") def check_request( self, request, method, params=None, data=None, headers=None): self.assertEqual(request.method, method) if params is not None: url = urlparse.urlparse(request.url) qs = urlparse.parse_qsl(url.query) self.assertEqual(dict(qs), params) if headers is not None: for key, value in headers.items(): self.assertEqual(request.headers[key], value) if data is None: self.assertEqual(request.body, None) else: self.assertEqual(json.loads(request.body), data) def test_get_metric(self): response = {u'stores.store_name.metric_name.agg': [{u'x': 1413936000000, u'y': 88916.0}, {u'x': 1414022400000, u'y': 91339.0}, {u'x': 1414108800000, u'y': 92490.0}, {u'x': 1414195200000, u'y': 92655.0}, {u'x': 1414281600000, u'y': 92786.0}]} adapter = RecordingAdapter(json.dumps(response)) self.session.mount( "http://example.com/api/v1/go/" "metrics/", adapter) result = self.client.get_metric( "stores.store_name.metric_name.agg", "-30d", "1d", "omit") self.assertEqual(result, response) self.check_request( adapter.request, 'GET', params={ "m": "stores.store_name.metric_name.agg", "interval": "1d", "from": "-30d", "nulls": "omit"}, headers={"Authorization": u'Bearer auth-token'}) def test_fire(self): response = [{ 'name': 'foo.last', 'value': 3.1415, 'aggregator': 'last', }] adapter = RecordingAdapter(json.dumps(response)) self.session.mount( "http://example.com/api/v1/go/" "metrics/", adapter) result = self.client.fire({ "foo.last": 3.1415, }) self.assertEqual(result, response) self.check_request( adapter.request, 'POST', data={"foo.last": 3.1415}, headers={"Authorization": u'Bearer auth-token'})
class TestHttpApiSender(TestCase): def setUp(self): self.session = TestSession() self.sender = HttpApiSender( account_key="acc-key", conversation_key="conv-key", api_url="http://example.com/api/v1/go/http_api_nostream", conversation_token="conv-token", session=self.session) def test_default_session(self): import requests sender = HttpApiSender( account_key="acc-key", conversation_key="conv-key", conversation_token="conv-token") self.assertTrue(isinstance(sender.session, requests.Session)) def test_default_api_url(self): sender = HttpApiSender( account_key="acc-key", conversation_key="conv-key", conversation_token="conv-token") self.assertEqual(sender.api_url, "http://go.vumi.org/api/v1/go/http_api_nostream") def check_request(self, request, method, data=None, headers=None): self.assertEqual(request.method, method) if data is not None: self.assertEqual(json.loads(request.body), data) if headers is not None: for key, value in headers.items(): self.assertEqual(request.headers[key], value) def test_send_text(self): adapter = RecordingAdapter(json.dumps({"message_id": "id-1"})) self.session.mount( "http://example.com/api/v1/go/http_api_nostream/conv-key/" "messages.json", adapter) result = self.sender.send_text("to-addr-1", "Hello!") self.assertEqual(result, { "message_id": "id-1", }) self.check_request( adapter.request, 'PUT', data={"content": "Hello!", "to_addr": "to-addr-1"}, headers={"Authorization": u'Basic YWNjLWtleTpjb252LXRva2Vu'}) def test_fire_metric(self): adapter = RecordingAdapter( json.dumps({"success": True, "reason": "Yay"})) self.session.mount( "http://example.com/api/v1/go/http_api_nostream/conv-key/" "metrics.json", adapter) result = self.sender.fire_metric("metric-1", 5.1, agg="max") self.assertEqual(result, { "success": True, "reason": "Yay", }) self.check_request( adapter.request, 'PUT', data=[["metric-1", 5.1, "max"]], headers={"Authorization": u'Basic YWNjLWtleTpjb252LXRva2Vu'}) def test_fire_metric_default_agg(self): adapter = RecordingAdapter( json.dumps({"success": True, "reason": "Yay"})) self.session.mount( "http://example.com/api/v1/go/http_api_nostream/conv-key/" "metrics.json", adapter) result = self.sender.fire_metric("metric-1", 5.2) self.assertEqual(result, { "success": True, "reason": "Yay", }) self.check_request( adapter.request, 'PUT', data=[["metric-1", 5.2, "last"]], headers={"Authorization": u'Basic YWNjLWtleTpjb252LXRva2Vu'})
class TestContactsApiClient(TestCase): API_URL = "http://example.com/go" AUTH_TOKEN = "auth_token" MAX_CONTACTS_PER_PAGE = 10 def setUp(self): self.contacts_data = {} self.groups_data = {} self.contacts_backend = FakeContactsApi( "go/", self.AUTH_TOKEN, self.contacts_data, self.groups_data, contacts_limit=self.MAX_CONTACTS_PER_PAGE) self.session = TestSession() self.adapter = FakeContactsApiAdapter(self.contacts_backend) self.simulate_api_up() def simulate_api_down(self): self.session.mount(self.API_URL, TestAdapter("API is down", 500)) def simulate_api_up(self): self.session.mount(self.API_URL, self.adapter) def make_client(self, auth_token=AUTH_TOKEN): return ContactsApiClient( auth_token, api_url=self.API_URL, session=self.session) def make_existing_contact(self, contact_data): existing_contact = make_contact_dict(contact_data) self.contacts_data[existing_contact[u"key"]] = existing_contact return existing_contact def make_existing_group(self, group_data): existing_group = make_group_dict(group_data) self.groups_data[existing_group[u'key']] = existing_group return existing_group def make_n_contacts(self, n, groups=None): contacts = [] for i in range(n): data = { u"msisdn": u"+155564%d" % (i,), u"name": u"Arthur", u"surname": u"of Camelot", } if groups is not None: data["groups"] = groups contacts.append(self.make_existing_contact(data)) return contacts def assert_contacts_equal(self, contacts_a, contacts_b): contacts_a.sort(key=lambda d: d['msisdn']) contacts_b.sort(key=lambda d: d['msisdn']) self.assertEqual(contacts_a, contacts_b) def assert_contact_status(self, contact_key, exists=True): exists_status = (contact_key in self.contacts_data) self.assertEqual(exists_status, exists) def assert_group_status(self, group_key, exists=True): exists_status = (group_key in self.groups_data) self.assertEqual(exists_status, exists) def assert_http_error(self, expected_status, func, *args, **kw): try: func(*args, **kw) except HTTPError as err: self.assertEqual(err.response.status_code, expected_status) else: self.fail( "Expected HTTPError with status %s." % (expected_status,)) def assert_paged_exception(self, f, *args, **kw): try: f(*args, **kw) except Exception as err: self.assertTrue(isinstance(err, PagedException)) self.assertTrue(isinstance(err.cursor, unicode)) self.assertTrue(isinstance(err.error, Exception)) return err def test_assert_http_error(self): self.session.mount("http://bad.example.com/", TestAdapter("", 500)) def bad_req(): r = self.session.get("http://bad.example.com/") r.raise_for_status() # Fails when no exception is raised. self.assertRaises( self.failureException, self.assert_http_error, 404, lambda: None) # Fails when an HTTPError with the wrong status code is raised. self.assertRaises( self.failureException, self.assert_http_error, 404, bad_req) # Passes when an HTTPError with the expected status code is raised. self.assert_http_error(500, bad_req) # Non-HTTPError exceptions aren't caught. def raise_error(): raise ValueError() self.assertRaises(ValueError, self.assert_http_error, 404, raise_error) def test_default_session(self): import requests contacts = ContactsApiClient(self.AUTH_TOKEN) self.assertTrue(isinstance(contacts.session, requests.Session)) def test_default_api_url(self): contacts = ContactsApiClient(self.AUTH_TOKEN) self.assertEqual( contacts.api_url, "https://go.vumi.org/api/v1/go") def test_auth_failure(self): contacts = self.make_client(auth_token="bogus_token") self.assert_http_error(403, contacts.get_contact, "foo") def test_contacts_single_page(self): [expected_contact] = self.make_n_contacts(1) contacts_api = self.make_client() [contact] = list(contacts_api.contacts()) self.assertEqual(contact, expected_contact) def test_contacts_no_results(self): contacts_api = self.make_client() contacts = list(contacts_api.contacts()) self.assertEqual(contacts, []) def test_contacts_multiple_pages(self): expected_contacts = self.make_n_contacts( self.MAX_CONTACTS_PER_PAGE + 1) contacts_api = self.make_client() contacts = list(contacts_api.contacts()) self.assert_contacts_equal(contacts, expected_contacts) def test_contacts_multiple_pages_with_cursor(self): expected_contacts = self.make_n_contacts( self.MAX_CONTACTS_PER_PAGE + 1) contacts_api = self.make_client() first_page = contacts_api._api_request("GET", "contacts", "") cursor = first_page['cursor'] contacts = list(contacts_api.contacts(start_cursor=cursor)) contacts.extend(first_page['data']) self.assert_contacts_equal(contacts, expected_contacts) def test_contacts_multiple_pages_with_failure(self): expected_contacts = self.make_n_contacts( self.MAX_CONTACTS_PER_PAGE + 1) contacts_api = self.make_client() it = contacts_api.contacts() contacts = [it.next() for _ in range(self.MAX_CONTACTS_PER_PAGE)] self.simulate_api_down() err = self.assert_paged_exception(it.next) self.simulate_api_up() [last_contact] = list(contacts_api.contacts(start_cursor=err.cursor)) self.assert_contacts_equal( contacts + [last_contact], expected_contacts) def test_create_contact(self): contacts = self.make_client() contact_data = { u"msisdn": u"+15556483", u"name": u"Arthur", u"surname": u"of Camelot", } contact = contacts.create_contact(contact_data) expected_contact = make_contact_dict(contact_data) # The key is generated for us. expected_contact[u"key"] = contact[u"key"] self.assertEqual(contact, expected_contact) self.assert_contact_status(contact[u"key"], exists=True) def test_create_contact_with_extras(self): contacts = self.make_client() contact_data = { u"msisdn": u"+15556483", u"name": u"Arthur", u"surname": u"of Camelot", u"extra": { u"quest": u"Grail", u"sidekick": u"Percy", }, } contact = contacts.create_contact(contact_data) expected_contact = make_contact_dict(contact_data) # The key is generated for us. expected_contact[u"key"] = contact[u"key"] self.assertEqual(contact, expected_contact) self.assert_contact_status(contact[u"key"], exists=True) def test_create_contact_with_key(self): contacts = self.make_client() contact_data = { u"key": u"foo", u"msisdn": u"+15556483", u"name": u"Arthur", u"surname": u"of Camelot", } self.assert_http_error(400, contacts.create_contact, contact_data) self.assert_contact_status(u"foo", exists=False) def test_get_contact(self): contacts = self.make_client() existing_contact = self.make_existing_contact({ u"msisdn": u"+15556483", u"name": u"Arthur", u"surname": u"of Camelot", }) contact = contacts.get_contact(existing_contact[u"key"]) self.assertEqual(contact, existing_contact) def test_get_contact_with_extras(self): contacts = self.make_client() existing_contact = self.make_existing_contact({ u"msisdn": u"+15556483", u"name": u"Arthur", u"surname": u"of Camelot", u"extra": { u"quest": u"Grail", u"sidekick": u"Percy", }, }) contact = contacts.get_contact(existing_contact[u"key"]) self.assertEqual(contact, existing_contact) def test_get_missing_contact(self): contacts = self.make_client() self.assert_http_error(404, contacts.get_contact, "foo") def test_get_contact_from_field(self): contacts = self.make_client() existing_contact = self.make_existing_contact({ u"msisdn": u"+15556483", u"name": u"Arthur", u"surname": u"of Camelot", }) contact = contacts.get_contact(msisdn='+15556483') self.assertEqual(contact, existing_contact) def test_get_contact_from_field_missing(self): contacts = self.make_client() self.make_existing_contact({ u"msisdn": u"+15556483", u"name": u"Arthur", u"surname": u"of Camelot", }) self.assert_http_error( 400, contacts.get_contact, msisdn='+12345') def test_update_contact(self): contacts = self.make_client() existing_contact = self.make_existing_contact({ u"msisdn": u"+15556483", u"name": u"Arthur", u"surname": u"of Camelot", }) new_contact = existing_contact.copy() new_contact[u"surname"] = u"Pendragon" contact = contacts.update_contact( existing_contact[u"key"], {u"surname": u"Pendragon"}) self.assertEqual(contact, new_contact) def test_update_contact_with_extras(self): contacts = self.make_client() existing_contact = self.make_existing_contact({ u"msisdn": u"+15556483", u"name": u"Arthur", u"surname": u"of Camelot", u"extra": { u"quest": u"Grail", u"sidekick": u"Percy", }, }) new_contact = existing_contact.copy() new_contact[u"surname"] = u"Pendragon" new_contact[u"extra"] = { u"quest": u"lunch", u"knight": u"Lancelot", } contact = contacts.update_contact(existing_contact[u"key"], { u"surname": u"Pendragon", u"extra": { u"quest": u"lunch", u"knight": u"Lancelot", }, }) self.assertEqual(contact, new_contact) def test_update_missing_contact(self): contacts = self.make_client() self.assert_http_error(404, contacts.update_contact, "foo", {}) def test_delete_contact(self): contacts = self.make_client() existing_contact = self.make_existing_contact({ u"msisdn": u"+15556483", u"name": u"Arthur", u"surname": u"of Camelot", }) self.assert_contact_status(existing_contact[u"key"], exists=True) contact = contacts.delete_contact(existing_contact[u"key"]) self.assertEqual(contact, existing_contact) self.assert_contact_status(existing_contact[u"key"], exists=False) def test_delete_missing_contact(self): contacts = self.make_client() self.assert_http_error(404, contacts.delete_contact, "foo") def test_create_group(self): client = self.make_client() group_data = { u'name': u'Bob', } group = client.create_group(group_data) expected_group = make_group_dict(group_data) # The key is generated for us. expected_group[u'key'] = group[u'key'] self.assertEqual(group, expected_group) self.assert_group_status(group[u'key'], exists=True) def test_create_smart_group(self): client = self.make_client() group_data = { u'name': u'Bob', u'query': u'test-query', } group = client.create_group(group_data) expected_group = make_group_dict(group_data) # The key is generated for us expected_group[u'key'] = group[u'key'] self.assertEqual(group, expected_group) self.assert_group_status(group[u'key'], exists=True) def test_create_group_with_key(self): client = self.make_client() group_data = { u'key': u'foo', u'name': u'Bob', u'query': u'test-query', } self.assert_http_error(400, client.create_group, group_data) def test_get_group(self): client = self.make_client() existing_group = self.make_existing_group({ u'name': 'Bob', }) group = client.get_group(existing_group[u'key']) self.assertEqual(group, existing_group) def test_get_smart_group(self): client = self.make_client() existing_group = self.make_existing_group({ u'name': 'Bob', u'query': 'test-query', }) group = client.get_group(existing_group[u'key']) self.assertEqual(group, existing_group) def test_get_missing_group(self): client = self.make_client() self.assert_http_error(404, client.get_group, 'foo') def test_update_group(self): client = self.make_client() existing_group = self.make_existing_group({ u'name': u'Bob', }) new_group = existing_group.copy() new_group[u'name'] = u'Susan' group = client.update_group(existing_group[u'key'], {'name': 'Susan'}) self.assertEqual(existing_group, group) self.assertEqual(group, new_group) def test_update_smart_group(self): client = self.make_client() existing_group = self.make_existing_group({ u'name': u'Bob', u'query': u'test-query', }) new_group = existing_group.copy() new_group[u'query'] = u'another-query' group = client.update_group(existing_group[u'key'], {'query': 'another-query'}) self.assertEqual(existing_group, group) self.assertEqual(group, new_group) def test_update_missing_group(self): client = self.make_client() self.assert_http_error(404, client.update_group, 'foo', {}) def test_delete_group(self): client = self.make_client() existing_group = self.make_existing_group({ u'name': u'Bob' }) self.assert_group_status(existing_group[u'key'], exists=True) group = client.delete_group(existing_group[u'key']) self.assertEqual(existing_group, group) self.assert_group_status(group[u'key'], exists=False) def test_delete_missing_group(self): client = self.make_client() self.assert_http_error(404, client.delete_group, 'foo') def test_group_contacts_multiple_pages_with_cursor(self): self.make_existing_group({ u'name': 'key', }) expected_contacts = self.make_n_contacts( self.MAX_CONTACTS_PER_PAGE + 1, groups=["key"]) client = self.make_client() first_page = client._api_request("GET", "groups/key", "contacts") cursor = first_page['cursor'] contacts = list(client.group_contacts(group_key="key", start_cursor=cursor)) contacts.extend(first_page['data']) contacts.sort(key=lambda d: d['msisdn']) expected_contacts.sort(key=lambda d: d['msisdn']) self.assertEqual(contacts, expected_contacts) def test_group_contacts_multiple_pages(self): self.make_existing_group({ u'name': 'key', }) self.make_existing_group({ u'name': 'diffkey', }) expected_contacts = self.make_n_contacts( self.MAX_CONTACTS_PER_PAGE + 1, groups=["key"]) self.make_existing_contact({ u"msisdn": u"+1234567", u"name": u"Nancy", u"surname": u"of Camelot", u"groups": ["diffkey"], }) client = self.make_client() contacts = list(client.group_contacts("key")) self.assert_contacts_equal(contacts, expected_contacts) def test_group_contacts_multiple_pages_with_failure(self): self.make_existing_group({ u'name': 'key', }) self.make_existing_group({ u'name': 'diffkey', }) expected_contacts = self.make_n_contacts( self.MAX_CONTACTS_PER_PAGE + 1, groups=["key"]) self.make_existing_contact({ u"msisdn": u"+1234567", u"name": u"Nancy", u"surname": u"of Camelot", u"groups": ["diffkey"], }) contacts_api = self.make_client() it = contacts_api.group_contacts("key") contacts = [it.next() for _ in range(self.MAX_CONTACTS_PER_PAGE)] self.simulate_api_down() err = self.assert_paged_exception(it.next) self.simulate_api_up() [last_contact] = list(contacts_api.group_contacts( "key", start_cursor=err.cursor)) self.assert_contacts_equal( contacts + [last_contact], expected_contacts) def test_group_contacts_none_found(self): self.make_existing_group({ u'name': 'key', }) self.make_existing_group({ u'name': 'diffkey', }) self.make_n_contacts( self.MAX_CONTACTS_PER_PAGE + 1, groups=["diffkey"]) client = self.make_client() contacts = list(client.group_contacts("key")) self.assert_contacts_equal(contacts, [])