def test_send_fcm_retry_backoff_time(self): client = MobileClient(parent=ndb.Key(Account, 'user_id'), user_id='user_id', messaging_id='messaging_id', client_type=ClientType.OS_IOS) client.put() import time batch_response = messaging.BatchResponse([ messaging.SendResponse(None, QuotaExceededError('code', 'message')) ]) for i in range(0, 6): with patch.object( FCMRequest, 'send', return_value=batch_response), patch('logging.error'): call_time = time.time() TBANSHelper._send_fcm([client], MockNotification(), i) # Check that we queue'd for a retry with the proper countdown time tasks = self.taskqueue_stub.get_filtered_tasks( queue_names='push-notifications') if i > 0: self.assertGreater(tasks[0].eta_posix - call_time, 0) # Make sure our taskqueue tasks execute what we expect with patch.object(TBANSHelper, '_send_fcm') as mock_send_fcm: deferred.run(tasks[0].payload) mock_send_fcm.assert_called_once_with([client], ANY, i + 1) self.taskqueue_stub.FlushQueue('push-notifications')
def test_send_fcm_unavailable_error(self): client = MobileClient(parent=ndb.Key(Account, 'user_id'), user_id='user_id', messaging_id='messaging_id', client_type=ClientType.OS_IOS) client.put() # Sanity check self.assertEqual(fcm_messaging_ids('user_id'), ['messaging_id']) batch_response = messaging.BatchResponse([ messaging.SendResponse(None, UnavailableError('code', 'message')) ]) with patch.object(FCMRequest, 'send', return_value=batch_response), patch( 'logging.error') as mock_error: exit_code = TBANSHelper._send_fcm([client], MockNotification()) self.assertEqual(exit_code, 0) mock_error.assert_called_once_with( 'FCM unavailable - retrying client...') # Sanity check self.assertEqual(fcm_messaging_ids('user_id'), ['messaging_id']) # Check that we queue'd for a retry tasks = self.taskqueue_stub.get_filtered_tasks( queue_names='push-notifications') self.assertEqual(len(tasks), 1) # Make sure our taskqueue tasks execute what we expect with patch.object(TBANSHelper, '_send_fcm') as mock_send_fcm: deferred.run(tasks[0].payload) mock_send_fcm.assert_called_once_with([client], ANY, 1)
def test_send_fcm_unhandled_error(self): client = MobileClient(parent=ndb.Key(Account, 'user_id'), user_id='user_id', messaging_id='messaging_id', client_type=ClientType.OS_IOS) client.put() # Sanity check self.assertEqual(fcm_messaging_ids('user_id'), ['messaging_id']) batch_response = messaging.BatchResponse( [messaging.SendResponse(None, FirebaseError('code', 'message'))]) with patch.object(FCMRequest, 'send', return_value=batch_response), patch( 'logging.error') as mock_error: exit_code = TBANSHelper._send_fcm([client], MockNotification()) self.assertEqual(exit_code, 0) mock_error.assert_called_once_with( 'Unhandled FCM error for messaging_id - code / message') # Sanity check self.assertEqual(fcm_messaging_ids('user_id'), ['messaging_id']) # Check that we didn't queue for a retry tasks = self.taskqueue_stub.get_filtered_tasks( queue_names='push-notifications') self.assertEqual(len(tasks), 0)
def test_send_fcm_sender_id_mismatch_error(self): client = MobileClient(parent=ndb.Key(Account, 'user_id'), user_id='user_id', messaging_id='messaging_id', client_type=ClientType.OS_IOS) client.put() # Sanity check self.assertEqual(fcm_messaging_ids('user_id'), ['messaging_id']) batch_response = messaging.BatchResponse([ messaging.SendResponse(None, SenderIdMismatchError('code', 'message')) ]) with patch.object(FCMRequest, 'send', return_value=batch_response), \ patch.object(MobileClient, 'delete_for_messaging_id', wraps=MobileClient.delete_for_messaging_id) as mock_delete, \ patch('logging.info') as mock_info: exit_code = TBANSHelper._send_fcm([client], MockNotification()) mock_delete.assert_called_once_with('messaging_id') self.assertEqual(exit_code, 0) mock_info.assert_called_with( 'Deleting mismatched client with ID: messaging_id') # Sanity check self.assertEqual(fcm_messaging_ids('user_id'), []) # Make sure we haven't queued for a retry tasks = self.taskqueue_stub.get_filtered_tasks( queue_names='push-notifications') self.assertEqual(len(tasks), 0)
def test_send_fcm_invalid_argument_error(self): client = MobileClient(parent=ndb.Key(Account, 'user_id'), user_id='user_id', messaging_id='messaging_id', client_type=ClientType.OS_IOS) client.put() # Sanity check self.assertEqual(fcm_messaging_ids('user_id'), ['messaging_id']) batch_response = messaging.BatchResponse([ messaging.SendResponse(None, InvalidArgumentError('code', 'message')) ]) with patch.object(FCMRequest, 'send', return_value=batch_response), patch( 'logging.critical') as mock_critical: exit_code = TBANSHelper._send_fcm([client], MockNotification()) self.assertEqual(exit_code, 0) mock_critical.assert_called_once_with( 'Invalid argument when sending to FCM - code') # Sanity check self.assertEqual(fcm_messaging_ids('user_id'), ['messaging_id']) # Make sure we haven't queued for a retry tasks = self.taskqueue_stub.get_filtered_tasks( queue_names='push-notifications') self.assertEqual(len(tasks), 0)
def post(self): self._require_login("/account/register") self._require_registration("/account/register") # Check to make sure that they aren't trying to edit another user current_user_account_id = self.user_bundle.account.key.id() target_account_id = self.request.get("account_id") if target_account_id == current_user_account_id: url = self.request.get("url") secret_key = self.request.get("secret") query = MobileClient.query( MobileClient.messaging_id == url, ancestor=ndb.Key(Account, current_user_account_id) ) if query.count() == 0: # Webhook doesn't exist, add it verification_key = NotificationHelper.verify_webhook(url, secret_key) client = MobileClient( parent=self.user_bundle.account.key, user_id=current_user_account_id, messaging_id=url, display_name=self.request.get("name"), secret=secret_key, client_type=ClientType.WEBHOOK, verified=False, verification_code=verification_key, ) client.put() else: # Webhook already exists. Update the secret current = query.fetch()[0] current.secret = secret_key current.put() self.redirect("/account") else: self.redirect("/")
def post(self): self._require_login() self._require_registration() # Check to make sure that they aren't trying to edit another user current_user_account_id = self.user_bundle.account.key.id() target_account_id = self.request.get('account_id') if target_account_id == current_user_account_id: url = self.request.get('url') secret_key = self.request.get('secret') query = MobileClient.query(MobileClient.messaging_id == url, ancestor=ndb.Key( Account, current_user_account_id)) if query.count() == 0: # Webhook doesn't exist, add it verification_key = NotificationHelper.verify_webhook( url, secret_key) client = MobileClient(parent=self.user_bundle.account.key, user_id=current_user_account_id, messaging_id=url, display_name=self.request.get('name'), secret=secret_key, client_type=ClientType.WEBHOOK, verified=False, verification_code=verification_key) client.put() else: # Webhook already exists. Update the secret current = query.fetch()[0] current.secret = secret_key current.put() self.redirect('/account') else: self.redirect('/')
def test_clients_empty(self): abc = MobileClient( parent=ndb.Key(Account, 'abc'), user_id='abc', messaging_id='token', client_type=ClientType.OS_IOS, device_uuid='uuid', display_name='Phone' ) abc.put() unverified = MobileClient( parent=ndb.Key(Account, 'efg'), user_id='efg', messaging_id='token', client_type=ClientType.OS_IOS, device_uuid='uuid', display_name='Phone', verified=False ) unverified.put() # Test empty users returns empty self.assertEqual(MobileClient.clients(users=[]), []) # Test empty client types return empty self.assertEqual(MobileClient.clients(users=['abc'], client_types=[]), []) # Test empty users and client types returns empty self.assertEqual(MobileClient.clients(users=[], client_types=[]), []) # Test client type + users does not return empty self.assertEqual(MobileClient.clients(users=['abc']), [abc]) # Test fetching for only verified self.assertEqual(MobileClient.clients(users=['efg']), [])
def test_broadcast_fcm(self): for client_type in ClientType.FCM_CLIENTS: client = MobileClient(parent=ndb.Key(Account, 'user_id'), user_id='user_id', messaging_id='token', client_type=client_type, device_uuid='uuid', display_name='Phone') client_key = client.put() from notifications.base_notification import BaseNotification with patch.object(BaseNotification, 'send') as mock_send: TBANSHelper.broadcast([client_type], 'Broadcast', 'Test broadcast') # Make sure we didn't send to Android mock_send.assert_not_called() # Make sure we'll send to FCM clients tasks = self.taskqueue_stub.get_filtered_tasks( queue_names='push-notifications') self.assertEqual(len(tasks), 1) # Make sure our taskqueue tasks execute what we expect with patch.object(TBANSHelper, '_send_fcm') as mock_send_fcm: deferred.run(tasks[0].payload) mock_send_fcm.assert_called_once_with([client], ANY) # Make sure the notification is a BroadcastNotification notification = mock_send_fcm.call_args[0][1] self.assertTrue(isinstance(notification, BroadcastNotification)) self.taskqueue_stub.FlushQueue('push-notifications') client_key.delete()
def test_defer_webhook(self): client = MobileClient(parent=ndb.Key(Account, 'user_id'), user_id='user_id', messaging_id='messaging_id', client_type=ClientType.WEBHOOK) client.put() notification = MockNotification() TBANSHelper._defer_webhook([client], notification) # Make sure we'll send to FCM clients tasks = self.taskqueue_stub.get_filtered_tasks( queue_names='push-notifications') self.assertEqual(len(tasks), 1) # Make sure our taskqueue tasks execute what we expect with patch.object(TBANSHelper, '_send_webhook') as mock_send_webhook: deferred.run(tasks[0].payload) mock_send_webhook.assert_called_once_with([client], ANY)
def test_broadcast_android(self): client_type = ClientType.OS_ANDROID messaging_id = 'token' client = MobileClient(parent=ndb.Key(Account, 'user_id'), user_id='user_id', messaging_id=messaging_id, client_type=client_type, device_uuid='uuid', display_name='Phone') client.put() from notifications.broadcast import BroadcastNotification with patch.object(BroadcastNotification, 'send') as mock_send: TBANSHelper.broadcast([client_type], 'Broadcast', 'Test broadcast') mock_send.assert_called_once_with({client_type: [messaging_id]}) # Make sure we didn't send to FCM or webhooks tasks = self.taskqueue_stub.GetTasks('push-notifications') self.assertEqual(len(tasks), 0)
def test_clients_multiple(self): abc = MobileClient( parent=ndb.Key(Account, 'abc'), user_id='abc', messaging_id='token', client_type=ClientType.OS_IOS, device_uuid='uuid', display_name='Phone' ) abc.put() efg = MobileClient( parent=ndb.Key(Account, 'efg'), user_id='efg', messaging_id='token', client_type=ClientType.OS_IOS, device_uuid='uuid', display_name='Phone' ) efg.put() self.assertEqual(MobileClient.clients(['abc', 'efg']), [abc, efg])
def post(self): self._require_registration() self._require_request_user_is_bundle_user() # Name and URL must be non-None url = self.request.get('url', None) name = self.request.get('name', None) if not url or not name: return self.redirect('/webhooks/add?error=1') # Secret may be none - but we'll generate a secret for the user secret = self.request.get('secret', None) if not secret: import uuid secret = uuid.uuid4().hex current_user_account_id = self.user_bundle.account.key.id() query = MobileClient.query(MobileClient.messaging_id == url, ancestor=ndb.Key(Account, current_user_account_id)) if query.count() == 0: # Webhook doesn't exist, add it from helpers.tbans_helper import TBANSHelper response = TBANSHelper.verify_webhook(url, secret) client = MobileClient( parent=self.user_bundle.account.key, user_id=current_user_account_id, messaging_id=url, display_name=name, secret=secret, client_type=ClientType.WEBHOOK, verified=False, verification_code=response.verification_key) client.put() else: # Webhook already exists. Update the secret current = query.fetch()[0] current.secret = secret current.put() self.redirect('/account')
def post(self): self._require_registration() self._require_request_user_is_bundle_user() # Name and URL must be non-None url = self.request.get('url', None) name = self.request.get('name', None) if not url or not name: return self.redirect('/webhooks/add?error=1') # Always generate secret server-side; previously allowed clients to set the secret import uuid secret = uuid.uuid4().hex current_user_account_id = self.user_bundle.account.key.id() query = MobileClient.query(MobileClient.messaging_id == url, ancestor=ndb.Key(Account, current_user_account_id)) if query.count() == 0: # Webhook doesn't exist, add it from helpers.tbans_helper import TBANSHelper verification_key = TBANSHelper.verify_webhook(url, secret) client = MobileClient( parent=self.user_bundle.account.key, user_id=current_user_account_id, messaging_id=url, display_name=name, secret=secret, client_type=ClientType.WEBHOOK, verified=False, verification_code=verification_key) client.put() else: # Webhook already exists. Update the secret current = query.fetch()[0] current.secret = secret current.put() self.redirect('/account')
def test_send(self): expected = [ 'client_type_{}'.format(client_type) for client_type in ClientType.FCM_CLIENTS ] clients = [ MobileClient(parent=ndb.Key(Account, 'user_id'), user_id='user_id', messaging_id='client_type_{}'.format(client_type), client_type=client_type) for client_type in ClientType.names.keys() ] # Insert an unverified webhook, just to test unverified = MobileClient(parent=ndb.Key(Account, 'user_id'), user_id='user_id', messaging_id='client_type_2', client_type=ClientType.WEBHOOK, verified=False) unverified.put() for c in clients: c.put() expected_fcm = [ c for c in clients if c.client_type in ClientType.FCM_CLIENTS ] expected_webhook = [ c for c in clients if c.client_type == ClientType.WEBHOOK ] notification = MockNotification() with patch.object(TBANSHelper, '_defer_fcm') as mock_fcm, patch.object( TBANSHelper, '_defer_webhook') as mock_webhook: TBANSHelper._send(['user_id'], notification) mock_fcm.assert_called_once_with(expected_fcm, notification) mock_webhook.assert_called_once_with(expected_webhook, notification)