def test_get_gifts_by_given_to(self): """Gifts endpoint which retrieves all gifts by given_to list ( methods = [ POST ] ).""" with self.app.app_context(): url = '/donation/gifts/given-to' # The given_to field enums: [ 'ACTION', 'NERF', 'SUPPORT' ] given_tos = GiftModel.given_to.property.columns[0].type.enums # Ensure that with no database entries endpoint returns nothing. response = self.test_client.post(url, data=json.dumps( {'given_to': given_tos[0]}), content_type='application/json', headers=self.headers) data_returned = json.loads(response.data.decode('utf-8')) self.assertEqual(len(data_returned), 0) # Create one gift for each given_to. gift_models = [] for given_to in given_tos: gift_model = from_json(GiftSchema(), get_gift_dict({ 'searchable_id': uuid.uuid4(), 'given_to': given_to }), create=True) gift_models.append(gift_model.data) # Create one gift with the same enumeration for return of multiple gifts. gift_model = from_json(GiftSchema(), get_gift_dict({ 'searchable_id': uuid.uuid4(), 'given_to': given_tos[0] }), create=True) gift_models.append(gift_model.data) database.session.bulk_save_objects(gift_models) database.session.commit() # Ensure that with the one duplicated given_to two gifts are returned. response = self.test_client.post(url, data=json.dumps( {'given_to': given_tos[0]}), content_type='application/json', headers=self.headers) data_returned = json.loads(response.data.decode('utf-8')) self.assertEqual(len(data_returned), 2) # Ensure that with 2 given_tos 3 gifts are returned. response = self.test_client.post( url, data=json.dumps({'given_to': [given_tos[0], given_tos[1]]}), content_type='application/json', headers=self.headers) data_returned = json.loads(response.data.decode('utf-8')) self.assertEqual(len(data_returned), 3)
def post( self ): """Endpoint to return several Gifts from table given a date or a range of dates for attached Transactions.""" gifts = get_gifts_by_date( request.json[ 'date' ] ) # We are using dump many=True and so if a single gift returned need to put it in a list. schema = GiftSchema( many=True ) result = schema.dump( gifts ).data return result, status.HTTP_200_OK
def post( self ): """Simple endpoint to return several rows from table given a list of ID's.""" if not request.json[ 'searchable_ids' ]: return [] gifts = get_gifts_by_searchable_ids( request.json[ 'searchable_ids' ] ) schema = GiftSchema( many=True ) result = schema.dump( gifts ).data return result, status.HTTP_200_OK
def test_get_gifts_by_user_id_get(self): """Gifts endpoint which retrieves all gifts with given user_id ( methods = [ GET ] ).""" with self.app.app_context(): url = '/donation/gift/user/{}' # Ensure that with no database entries endpoint returns nothing. response = self.test_client.get(url.format(1), headers=self.headers) data_returned = json.loads(response.data.decode('utf-8')) self.assertEqual(len(data_returned), 0) # Create a gift with a user_id. gift_model = from_json(GiftSchema(), get_gift_dict({ 'searchable_id': uuid.uuid4(), 'user_id': 1 }), create=True) database.session.add(gift_model.data) database.session.commit() # Ensure that with a user_id on a gift it is returned response = self.test_client.get(url.format(1), headers=self.headers) data_returned = json.loads(response.data.decode('utf-8')) self.assertEqual(len(data_returned), 1)
def test_gift_model(self): """A test to ensure that gifts are saved correctly to the database.""" with self.app.app_context(): gift_dict = get_gift_dict({ 'user_id': 1, 'recurring_subscription_id': 'abcdefg' }) gift_model = from_json(GiftSchema(), gift_dict, create=True) database.session.add(gift_model.data) database.session.commit() gift_query = GiftModel.query.filter_by( user_id=gift_dict['user_id']).one() gift_session = database.session.query(GiftModel).filter_by( user_id=gift_dict['user_id']).one() kwargs = { 'self': self, 'model_dict': gift_dict, 'model': GiftModel, 'model_data': gift_model.data, 'model_query': gift_query, 'model_session': gift_session } ensure_query_session_aligned(kwargs)
def test_get_transactions_by_gift( self ): """Retrieves all transactions with a specified gift searchable_id ( methods = [ GET ] ).""" with self.app.app_context(): # The parameter is for the searchable_id. url = '/donation/gifts/{}/transactions' # Create 3 gifts to attach transactions to. # Create each gift with a reproducible UUID. total_gifts = 5 searchable_ids = get_gift_searchable_ids() gift_models = [ ] for i in range( 0, total_gifts ): # pylint: disable=W0612 gift_json = get_gift_dict() del gift_json[ 'id' ] gift_json[ 'searchable_id' ] = searchable_ids[ i ] gift_model = GiftSchema().load( gift_json ).data gift_models.append( gift_model ) database.session.bulk_save_objects( gift_models ) database.session.commit() # Create 3 transactions attached to the same gift ID = 1, one transaction to 4 and none on 5. total_transactions = 5 transaction_models = [] for i in range( 0, total_transactions ): # pylint: disable=W0612 transaction_json = get_transaction_dict() del transaction_json[ 'id' ] if i <= 2: transaction_json[ 'gift_id' ] = 1 transaction_json[ 'gift_searchable_id' ] = uuid.UUID( searchable_ids[ 0 ] ).hex elif i == 3: transaction_json[ 'gift_id' ] = i transaction_json[ 'gift_searchable_id' ] = uuid.UUID( searchable_ids[ i ] ).hex else: transaction_json[ 'gift_id' ] = i transaction_json[ 'gift_searchable_id' ] = uuid.UUID( searchable_ids[ i ] ).hex transaction_model = TransactionSchema().load( transaction_json ).data transaction_models.append( transaction_model ) database.session.bulk_save_objects( transaction_models ) database.session.commit() # searchable_ids[ 0 ] is gift ID = 1 and will have 3 transactions. response = self.test_client.get( url.format( uuid.UUID( searchable_ids[ 0 ] ).hex ), headers=self.headers ) self.assertEqual( len( json.loads( response.data.decode( 'utf-8' ) ) ), 3 ) # searchable_ids[ 2 ] is gift ID = 3 and will have 1 transactions. response = self.test_client.get( url.format( uuid.UUID( searchable_ids[ 2 ] ).hex ), headers=self.headers ) self.assertEqual( len( json.loads( response.data.decode( 'utf-8' ) ) ), 1 ) # searchable_ids[ 4 ] is gift ID = 5 and will have 0 transactions. response = self.test_client.get( url.format( uuid.UUID( searchable_ids[ 4 ] ).hex ), headers=self.headers ) self.assertEqual( len( json.loads( response.data.decode( 'utf-8' ) ) ), 0 )
def get( self ): """Simple endpoint to retrieve all rows from table.""" query_terms = build_filter_from_request_args( request.args ) link_header_query_terms = None # Build the page and sort information from the filter results on the query string. # Delete these keys and pass along only the model filter/search terms. page_information = {} sort_information = [] if query_terms: page_information = {} if 'paginate' in query_terms and query_terms[ 'paginate' ]: page_information = { 'page_number': query_terms[ 'paginate' ][ 'page_number' ], 'rows_per_page': query_terms[ 'paginate' ][ 'rows_per_page' ] } del query_terms[ 'paginate' ] link_header_query_terms = copy.deepcopy( query_terms ) if 'sort' in query_terms and query_terms[ 'sort' ]: sort_information = query_terms[ 'sort' ] del query_terms[ 'sort' ] gifts = get_gifts( query_terms, page_information=page_information, sort_information=sort_information ) if page_information: transformed_data = transform_data( 'donation/gifts', link_header_query_terms, gifts, GiftSchema ) response = jsonify( transformed_data[ 'page' ] ) response.headers[ 'Link' ] = transformed_data[ 'link-header' ] response.status_code = status.HTTP_200_OK return response schema = GiftSchema( many=True ) result = schema.dump( gifts ).data return result, status.HTTP_200_OK
def create_gift_and_transaction(): """A function to create a gift and an attached transaction for a given Braintree sale. Sometimes while testing a developer will need to have a specific gift and transaction in the database that would have been created for a Braintree sale. This function allows you to specify Braintree reference numbers, e.g. transaction sale ID and the subscription reference number, and create the gift and transaction associated with that sale. """ with app.app_context(): # These are the Braintree subscription ID and transaction sale ID to create a gift and transaction for. # Change this to suit your needs. subscription_id = 'kfwgzr' transaction_id = '83afbynd' date_start = datetime.utcnow().replace( hour=23, minute=59, second=59, microsecond=9999 ) utc_dates = [ date_start - timedelta( hours=hours, minutes=30 ) for hours in range( 1, 25 ) ] pairs = 1 for date_in_utc in utc_dates: i = 0 while i < pairs: i += 1 # Create a gift. gift_json = get_gift_dict( { 'recurring_subscription_id': subscription_id } ) gift_json[ 'searchable_id' ] = uuid.uuid4() del gift_json[ 'id' ] gift_model = GiftSchema().load( gift_json ).data database.session.add( gift_model ) database.session.flush() gift_id = gift_model.id # Create 4 transactions per each gift. transaction_json = get_transaction_dict( { 'gift_id': gift_id } ) transaction_json[ 'type' ] = 'Gift' transaction_json[ 'status' ] = 'Completed' del transaction_json[ 'id' ] transaction_model = TransactionSchema().load( transaction_json ).data transaction_model.reference_number = transaction_id transaction_model.date_in_utc = date_in_utc transaction_model.reference_number = transaction_id transaction_model.notes = '{} : {}'.format( str( 1 ), str( gift_json[ 'searchable_id' ] ) ) database.session.add( transaction_model ) database.session.commit()
def test_get_gifts_by_user_id_post(self): """Gifts endpoint which retrieves all gifts with list of given user_id's ( methods = [ POST ] ).""" with self.app.app_context(): url = '/donation/gift/user' # Ensure that with no database entries endpoint returns nothing. response = self.test_client.post(url, data=json.dumps( {'user_ids': [1, 3]}), content_type='application/json', headers=self.headers) data_returned = json.loads(response.data.decode('utf-8')) self.assertEqual(len(data_returned), 0) # Create a gift with a user_id. max_number_of_users = 5 gift_models = create_model_list(GiftSchema(), get_gift_dict(), max_number_of_users, 'user_id') database.session.bulk_save_objects(gift_models) database.session.commit() # Ensure that with no users endpoint returns no gifts. response = self.test_client.post(url, data=json.dumps({'user_ids': []}), content_type='application/json', headers=self.headers) data_returned = json.loads(response.data.decode('utf-8')) self.assertEqual(len(data_returned), 0) # Ensure that with one user endpoint returns one gift. response = self.test_client.post(url, data=json.dumps({'user_ids': [2]}), content_type='application/json', headers=self.headers) data_returned = json.loads(response.data.decode('utf-8')) self.assertEqual(len(data_returned), 1) # Ensure that with multiple users endpoint returns multiple gifts. response = self.test_client.post(url, data=json.dumps( {'user_ids': [2, 3, 4]}), content_type='application/json', headers=self.headers) data_returned = json.loads(response.data.decode('utf-8')) self.assertEqual(len(data_returned), 3)
def test_put_gift_update_note(self): """Gifts endpoint to add a note to a gift with searchable_id ( methods = [ PUT ] ).""" with self.app.app_context(): # Parameter in URL is for a searchable_id_prefix. url = '/donation/gift/{}/notes' agent_dict = get_agent_dict({ 'name': 'Aaron Peters', 'user_id': 3255162, 'type': 'Staff Member' }) agent_model = from_json(AgentSchema(), agent_dict, create=True) database.session.add(agent_model.data) gift_transactions = TransactionModel.query.all() # Ensure no transactions in the database. self.assertEqual(len(gift_transactions), 0) gift_json = get_gift_dict() del gift_json['id'] gift_json['searchable_id'] = uuid.uuid4() gift_model = GiftSchema().load(gift_json).data database.session.add(gift_model) database.session.commit() transaction_note = { 'enacted_by_agent_id': '5', 'note': 'Add this to the Gift please.' } response = self.test_client.put(url.format( gift_model.searchable_id.hex), data=json.dumps(transaction_note), content_type='application/json', headers=self.headers) self.assertEqual(response.status_code, status.HTTP_200_OK) # Make sure a transaction was added. gift_transactions = TransactionModel.query.all() self.assertEqual(len(gift_transactions), 1) self.assertEqual(gift_transactions[0].notes, transaction_note['note'])
def test_get_gifts(self): """Gifts endpoint with no ID's retrieves all ( methods = [ GET ] ).""" with self.app.app_context(): url = '/donation/gifts' # Ensure a GET with no saved gifts returns 0. response = self.test_client.get(url, headers=self.headers) self.assertEqual(len(json.loads(response.data.decode('utf-8'))), 0) # To create each gift with a new UUID call get_gift_dict() separately. total_gifts = 5 gift_models = [] for i in range(0, total_gifts): # pylint: disable=W0612 gift_json = get_gift_dict() del gift_json['id'] gift_json['searchable_id'] = uuid.uuid4() gift_model = GiftSchema().load(gift_json).data gift_models.append(gift_model) database.session.bulk_save_objects(gift_models) database.session.commit() # Ensure GET returns all gifts. response = self.test_client.get(url, headers=self.headers) self.assertEqual(len(json.loads(response.data.decode('utf-8'))), total_gifts) # Ensure GET retrieves 2 gifts and they have the correct ID's. searchable_ids = [ str(gift_models[0].searchable_id), str(gift_models[1].searchable_id) ] searchable_id_parameters = 'in:{},{}'.format( searchable_ids[0], searchable_ids[1]) url_with_parameters = '{}?searchable_id={}'.format( url, searchable_id_parameters) response = self.test_client.get(url_with_parameters, headers=self.headers) data_returned = json.loads(response.data.decode('utf-8')) self.assertEqual(len(data_returned), 2) self.assertEqual(data_returned[0]['searchable_id'], searchable_ids[0]) self.assertEqual(data_returned[1]['searchable_id'], searchable_ids[1])
def test_get_gifts_by_date_future(self): """Gifts endpoint which retrieves all gifts newer than date, or between 2 dates ( methods = [ POST ] ).""" with self.app.app_context(): url = '/donation/gifts/date' # Ensure that with no database entries endpoint returns nothing. date_in_utc_now = datetime.utcnow() response = self.test_client.post( url, data=json.dumps({'date': date_in_utc_now.strftime('%Y-%m-%d')}), content_type='application/json', headers=self.headers) data_returned = json.loads(response.data.decode('utf-8')) self.assertEqual(len(data_returned), 0) # Create some gifts to retrieve. total_gifts = 2 gift_models = create_model_list(GiftSchema(), get_gift_dict(), total_gifts) # Create a set of transactions and attach to a specific gift. # Here are the time deltas: { gift 1: [ 0, -2, -4, -6 ], gift 2: [ -8, -10, -12, -14 ] } total_transactions = 4 transaction_models = create_gift_transactions_date( TransactionSchema(), get_transaction_dict(), total_transactions, total_gifts) database.session.bulk_save_objects(gift_models) database.session.bulk_save_objects(transaction_models) database.session.commit() date_in_utc_now = datetime.utcnow() # Date in the future should bring back no results. date_in_utc = date_in_utc_now + timedelta(days=2) response = self.test_client.post( url, data=json.dumps({'date': date_in_utc.strftime('%Y-%m-%d')}), content_type='application/json', headers=self.headers) data_returned = json.loads(response.data.decode('utf-8')) self.assertEqual(len(data_returned), 0)
def build_models_sale(user, gift, transactions): """Given the dictionaries for the models go ahead and build them. :param dict user: User dictionary with necessary model fields, and may have additional fields. :param dict gift: Gift dictionary with necessary model fields, and may have additional fields. :param transactions: The list of transactions. If this is a Braintree sale, for example, there will be one transaction in the list. On the other hand if this is an administrative sale where the method used is a check or money order there will be 2 transactions. :return: """ # We are not caging at the front of the sale, and do that at the end. # The user is stored in QueuedDonorModel and the gift is given a user_id = -2 user_id = -2 # Build the gift model dictionary, and flush to get new auto-incremented gift_id. try: # Build the gift. if not gift['campaign_id']: gift['campaign_id'] = None elif not get_campaign_by_id(gift['campaign_id']): gift['campaign_id'] = get_campaigns_by_type('is_default', 1)[0].id gift['user_id'] = user_id gift_model = from_json(GiftSchema(), gift) database.session.add(gift_model.data) database.session.flush() gift_id = gift_model.data.id user['gift_id'] = gift_id user['gift_searchable_id'] = gift_model.data.searchable_id user['campaign_id'] = gift_model.data.campaign_id # Build the transactions. for transaction in transactions: transaction['gift_id'] = gift_id transaction_model = from_json(TransactionSchema(), transaction) database.session.add(transaction_model.data) database.session.flush() transaction['id'] = transaction_model.data.id database.session.commit() except: database.session.rollback() raise BuildModelsGiftTransactionsPathError()
def build_gift(gift_dict): """Given a dictionary for the Gift builds, commits and returns the model. Uses flush() so we can get the Gift ID. :param gift_dict: The Gift dictionary. :return: gift_model.data: The Gift model built from the given dictionary and has the new auto-incremented ID. """ with app.app_context(): gift_model = from_json(GiftSchema(), gift_dict, create=True) database.session.add(gift_model.data) database.session.flush() database.session.commit() print() print('Build GiftModel') print(' gift_model.id : {}'.format(gift_model.data.id)) print(' gift_model.given_to: {}'.format(gift_model.data.given_to)) print() return gift_model.data
def test_build_transaction( self ): """Transactions endpoint to add a transaction to a gift with searchable_id ( methods = [ POST ] ).""" with self.app.app_context(): # Parameter in URL is for a searchable_id_prefix. url = '/donation/gift/transaction' agent_dict = get_agent_dict( { 'name': 'Aaron Peters', 'user_id': 3255162, 'type': 'Staff Member' } ) agent_model = from_json( AgentSchema(), agent_dict, create=True ) database.session.add( agent_model.data ) # Create a gift to attach a transaction to. gift_json = get_gift_dict() del gift_json[ 'id' ] gift_json[ 'searchable_id' ] = uuid.uuid4() gift_model = GiftSchema().load( gift_json ).data database.session.add( gift_model ) database.session.commit() gift = GiftModel.query.all()[ 0 ] # Ensure no transactions currently on gift. self.assertEqual( len( gift.transactions ), 0 ) new_transaction = get_transaction_dict( { 'gift_searchable_id': gift_json[ 'searchable_id' ].hex, 'enacted_by_agent_id': None } ) self.test_client.post( url, data=json.dumps( new_transaction ), content_type='application/json', headers=self.headers ) # Ensure the new transactions is now on the gift. self.assertEqual( len( gift.transactions ), 1 ) self.assertEqual( gift.transactions[ 0 ].gift_searchable_id.hex, gift_json[ 'searchable_id' ].hex )
def test_get_gifts_partial_id(self): """Gifts endpoint to get a list of Gifts given a partial searchable_id ( methods = [ GET ] ).""" with self.app.app_context(): # Parameter in URL is for a searchable_id_prefix. url = '/donation/gifts/uuid_prefix/{}' # Create each gift with a reproducible UUID. total_gifts = 5 searchable_ids = get_gift_searchable_ids() gift_models = [] for i in range(0, total_gifts): # pylint: disable=W0612 gift_json = get_gift_dict() del gift_json['id'] gift_json['searchable_id'] = searchable_ids[i] gift_model = GiftSchema().load(gift_json).data gift_models.append(gift_model) database.session.bulk_save_objects(gift_models) database.session.commit() # searchable_ids[ 0:2 ] have the same first 5 characters: this test should return those 2. response = self.test_client.get(url.format(searchable_ids[0][:5]), headers=self.headers) self.assertEqual(len(json.loads(response.data.decode('utf-8'))), 2) # searchable_ids[ 1 ] shares first 5 characters, but is unique as a whole and should return 1. searchable_id_uuid = uuid.UUID(searchable_ids[1]).hex response = self.test_client.get(url.format(searchable_id_uuid), headers=self.headers) self.assertEqual(len(json.loads(response.data.decode('utf-8'))), 1) # searchable_ids[ 2 ] is unique and first 5 characters should return 1. response = self.test_client.get(url.format(searchable_ids[2][:5]), headers=self.headers) self.assertEqual(len(json.loads(response.data.decode('utf-8'))), 1) # searchable_ids[ 5 ] is not in the database. searchable_id_uuid = uuid.UUID(searchable_ids[5]).hex response = self.test_client.get(url.format(searchable_id_uuid), headers=self.headers) self.assertEqual(len(json.loads(response.data.decode('utf-8'))), 0)
def test_get_gifts_with_id(self): """Gifts-transaction endpoint with one gift ID retrieves all transactions on gift ( methods = [ GET ] ).""" with self.app.app_context(): url = '/donation/gifts/{}/transactions' # Ensure that with no database entries endpoint returns nothing. response = self.test_client.get(url.format(str(uuid.uuid4())), headers=self.headers) data_returned = json.loads(response.data.decode('utf-8')) self.assertEqual(len(data_returned), 0) # Create some gifts to retrieve. total_gifts = 5 gift_models = create_model_list(GiftSchema(), get_gift_dict(), total_gifts) # Create a set of transactions and attach to a specific gift. total_transactions = 5 transaction_gift_id = 3 transaction_models = create_model_list( TransactionSchema(), get_transaction_dict({'gift_id': transaction_gift_id}), total_transactions) database.session.bulk_save_objects(gift_models) database.session.bulk_save_objects(transaction_models) database.session.commit() # Build the URL using the searchable_id of the gift. gift_3 = GiftModel.query.filter_by(id=3).one_or_none() searchable_id = gift_3.searchable_id # Ensure GET retrieves the specified gift and all its transactions. response = self.test_client.get(url.format(str(searchable_id)), headers=self.headers) data_returned = json.loads(response.data.decode('utf-8')) self.assertEqual(len(data_returned), total_transactions)
def test_admin_record_bounced_check(self): """Test for recording a bounced check.""" with self.app.app_context(): # Create 2 gifts: one for the transaction that needs to record bounced check, the other to query against. # Create the bank and sourced by agents for recording a check. agent_dict = get_agent_dict({ 'name': 'Fidelity Bank', 'type': 'Organization' }) bank_agent_model = from_json(AgentSchema(), agent_dict, create=True) database.session.add(bank_agent_model.data) database.session.flush() agent_user_id = 3255162 agent_dict = get_agent_dict({ 'name': 'Aaron Peters', 'user_id': agent_user_id, 'type': 'Staff Member' }) user_agent_model = from_json(AgentSchema(), agent_dict, create=True) database.session.add(user_agent_model.data) database.session.flush() # Here is the first gift as check. gift_dict = get_gift_dict({ 'method_used_id': 3, 'sourced_from_agent_id': 1, 'reference_number': '1201', 'customer_id': '' }) gift_model = from_json(GiftSchema(), gift_dict, create=True) database.session.add(gift_model.data) database.session.flush() gift_searchable_id = gift_model.data.searchable_id # Create the 2nd gift as check. gift_dict = get_gift_dict({ 'method_used_id': 3, 'sourced_from_agent_id': 1 }) gift_model = from_json(GiftSchema(), gift_dict, create=True) database.session.add(gift_model.data) database.session.flush() # Create 2 transactions on the first gift for the check. transaction_dict = get_transaction_dict({ 'gift_id': 1, 'enacted_by_agent_id': 1, 'type': 'Gift', 'gross_gift_amount': Decimal('25.00'), 'reference_number': '1201' }) transaction_model = from_json(TransactionSchema(), transaction_dict, create=True) database.session.add(transaction_model.data) transaction_dict = get_transaction_dict({ 'gift_id': 1, 'enacted_by_agent_id': 2, 'type': 'Deposit to Bank', 'gross_gift_amount': Decimal('25.00'), 'reference_number': '<bank-deposit-number>' }) transaction_model = from_json(TransactionSchema(), transaction_dict, create=True) database.session.add(transaction_model.data) # Put a transaction on the second gift. transaction_dict = get_transaction_dict({ 'gift_id': 2, 'enacted_by_agent_id': 1, 'type': 'Refund', 'gross_gift_amount': Decimal('25.00') }) transaction_model = from_json(TransactionSchema(), transaction_dict, create=True) database.session.add(transaction_model.data) database.session.flush() database.session.commit() # Call the function to test. # Needs the JWT Ultsys agent user ID because calling function directly ( not through resource ). payload = { 'gift_searchable_id': gift_searchable_id, 'reference_number': '1201', 'transaction_notes': '', 'gross_gift_amount': '0.00', 'user_id': 3255162 } # Calling the function for recording the bounced check directly and bypassing JWT is resource. record_bounced_check = admin_record_bounced_check(payload) transaction_bounced_check = TransactionModel.query\ .filter_by( type=self.parameters[ 'transaction_type_bounced' ] ).one_or_none() self.assertEqual(record_bounced_check, True) self.assertEqual(transaction_bounced_check.enacted_by_agent_id, 2) self.assertEqual(transaction_bounced_check.type, self.parameters['transaction_type_bounced']) self.assertEqual(transaction_bounced_check.gross_gift_amount, self.parameters['gift_amount_bounced'])
def create_database_transactions(): """Uses Braintree sales during a specified interval to build the initial gift and transaction in the database. Very useful for filling the database and then running the transaction updater for testing. The transactions created here will have type 'Gift' and status 'Completed'. """ dates = {'month_0': 7, 'day_0': 1, 'month_1': 7, 'day_1': 31} date1 = datetime.utcnow().replace(month=dates['month_1'], day=dates['day_1'], hour=23, minute=59, second=59, microsecond=9999) date0 = datetime.utcnow().replace(month=dates['month_0'], day=dates['day_0'], hour=0, minute=0, second=0, microsecond=0) print('{} ~ {}'.format(date0.strftime(MODEL_DATE_STRING_FORMAT), date1.strftime(MODEL_DATE_STRING_FORMAT))) with app.app_context(): date_in_utc = datetime.fromtimestamp(0) sales_authorized = {} search_at(date0, date1, 'authorized_at', sales_authorized) for sales_id, sale in sales_authorized.items(): # pylint: disable=unused-variable gift_dict = { 'id': None, 'searchable_id': uuid.uuid4(), 'user_id': 999999999, 'method_used': 'Web Form Credit Card', 'sourced_from_agent_id': AGENT_ID, 'given_to': MERCHANT_ID_GIVEN_TO[sale.merchant_account_id], 'recurring_subscription_id': sale.subscription_id } gift_model = from_json(GiftSchema(), gift_dict) database.session.add(gift_model.data) database.session.flush() database.session.commit() for history_item in sale.status_history: date_in_utc = datetime.fromtimestamp(0) if history_item.status == 'authorized': date_in_utc = history_item.timestamp.strftime( MODEL_DATE_STRING_FORMAT) break transaction_dict = { 'gift_id': gift_model.data.id, 'date_in_utc': date_in_utc, 'enacted_by_agent_id': AGENT_ID, 'type': 'Gift', 'status': 'Completed', 'reference_number': sale.id, 'gross_gift_amount': sale.amount, 'fee': sale.service_fee_amount if sale.service_fee_amount else Decimal(0), 'notes': 'Automated creation of transaction.' } transaction_model = from_json(TransactionSchema(), transaction_dict) database.session.add(transaction_model.data) database.session.commit()
def post( self ): """Endpoint to return several Gifts from table given a given_to.""" gifts = get_gifts_by_given_to( request.json[ 'given_to' ] ) schema = GiftSchema( many=True ) result = schema.dump( gifts ).data return result, status.HTTP_200_OK
def test_get_transactions_by_gifts( self ): """Retrieves all transactions or those in a list of gift searchable_id's ( methods = [ GET, POST ] ).""" with self.app.app_context(): # The parameter is for the searchable_id. url = '/donation/gifts/transactions' # Create 3 gifts to attach transactions to. # Create each gift with a reproducible UUID. total_gifts = 5 searchable_ids = get_gift_searchable_ids() gift_models = [ ] for i in range( 0, total_gifts ): # pylint: disable=W0612 gift_json = get_gift_dict() del gift_json[ 'id' ] gift_json[ 'searchable_id' ] = searchable_ids[ i ] gift_model = GiftSchema().load( gift_json ).data gift_models.append( gift_model ) database.session.bulk_save_objects( gift_models ) database.session.commit() # Create 3 transactions attached to the same gift ID = 1, one transaction to 4 and none on 5. total_transactions = 5 transaction_models = [] for i in range( 0, total_transactions ): # pylint: disable=W0612 transaction_json = get_transaction_dict() del transaction_json[ 'id' ] if i <= 2: transaction_json[ 'gift_id' ] = 1 transaction_json[ 'gift_searchable_id' ] = uuid.UUID( searchable_ids[ 0 ] ).hex elif i == 3: transaction_json[ 'gift_id' ] = i transaction_json[ 'gift_searchable_id' ] = uuid.UUID( searchable_ids[ i ] ).hex else: transaction_json[ 'gift_id' ] = i transaction_json[ 'gift_searchable_id' ] = uuid.UUID( searchable_ids[ i ] ).hex transaction_model = TransactionSchema().load( transaction_json ).data transaction_models.append( transaction_model ) database.session.bulk_save_objects( transaction_models ) database.session.commit() # Get all transactions in the database. response = self.test_client.get( url, headers=self.headers ) self.assertEqual( len( json.loads( response.data.decode( 'utf-8' ) ) ), 5 ) # searchable_ids[ 0 ] is gift ID = 1 and will have 3 transactions: test string searchable ID. response = self.test_client.post( url, data=json.dumps( { 'searchable_ids': uuid.UUID( searchable_ids[ 0 ] ).hex } ), content_type='application/json', headers=self.headers ) self.assertEqual( len( json.loads( response.data.decode( 'utf-8' ) ) ), 3 ) # Gift ID = 1 and 2 and will have a total of 4 transactions: test list of searchable ID's. response = self.test_client.post( url, data=json.dumps( { 'searchable_ids': [ uuid.UUID( searchable_ids[ 0 ] ).hex, uuid.UUID( searchable_ids[ 2 ] ).hex ] } ), content_type='application/json', headers=self.headers ) self.assertEqual( len( json.loads( response.data.decode( 'utf-8' ) ) ), 4 )
def create_database_tables(): """A function to create the DONATE database tables, specifically the GiftModel with UUID. The GiftModel is build using Marshmallow schema GiftSchema, which deserializes a dictionary to the model: The searchable_id in the gift_json is: gift_json[ 'searchable_id' ] = uuid.uuid4() This gets passed to the GiftSchema where: searchable_id = fields.UUID() And so the validation step is passed. MySql does not have a UUID type though and there we have ( GiftModel ): searchable_id = database.Column( database.BINARY( 16 ), nullable=False, default=uuid.uuid4().bytes ) The helper model class BinaryUUID in binary_uuid.py handles the serialization in and out. """ with app.app_context(): type = { 3: 'Gift', 2: 'Deposit to Bank', 1: 'Dispute', 0: 'Refund' } # Create 100 gifts. for i in range( 0, 100 ): gift_json = get_gift_dict() del gift_json[ 'id' ] gift_json[ 'searchable_id' ] = uuid.uuid4() gift_model = GiftSchema().load( gift_json ).data # Add the index as a note for debugging Gifts, since they exclude ID. gift_model.notes = '{} : {}'.format( str( i + 1 ), str( gift_json[ 'searchable_id' ] ) ) database.session.add( gift_model ) database.session.flush() gift_id = gift_model.id # Create 4 transactions per each gift. transactions = [] start_datetime = datetime.utcnow() for j in range( 0, 4 ): # pylint: disable=unused-variable test_datetime = start_datetime - timedelta( days=j ) test_datetime = test_datetime.strftime( '%Y-%m-%d %H:%M:%S' ) transaction_json = get_transaction_dict( { 'gift_id': gift_id, 'date_in_utc': test_datetime, 'gross_gift_amount': Decimal( 25 - j ), 'type': type[ j ], 'status': 'Completed' } ) del transaction_json[ 'id' ] transaction_model = TransactionSchema().load( transaction_json ).data transactions.append( transaction_model ) database.session.bulk_save_objects( transactions ) # Create the agents. # agent_jsons = [ # { 'name': 'Donate API', 'user_id': None, 'staff_id': None, 'type': 'Automated' }, # { 'name': 'Braintree', 'user_id': None, 'staff_id': None, 'type': 'Organization' }, # { 'name': 'PayPal', 'user_id': None, 'staff_id': None, 'type': 'Organization' }, # { 'name': 'Credit Card Issuer', 'user_id': None, 'staf_id': None, 'type': 'Organization' }, # { 'name': 'Unspecified NumbersUSA Staff', 'user_id': None, 'staff_id': None, 'type': 'Staff Member' }, # { 'name': 'Dan Marsh', 'user_id': 1234, 'staff_id': 4321, 'type': 'Staff Member' }, # { 'name': 'Joshua Turcotte', 'user_id': 7041, 'staff_id': 1407, 'type': 'Staff Member' }, # { 'name': 'Donate API', 'user_id': None, 'staff_id': None, 'type': 'Automated' } # ] # agents = [] # for agent_json in agent_jsons: # agent_model = AgentSchema().load( agent_json ).data # agents.append( agent_model ) # database.session.bulk_save_objects( agents ) database.session.commit()
def post( self ): """Endpoint to return several Gifts from table given a list of user ID's.""" gifts = get_gifts_by_user_id( request.json[ 'user_ids' ] ) schema = GiftSchema( many=True ) result = schema.dump( gifts ).data return result, status.HTTP_200_OK
def get( self, user_id ): """Endpoint to return several Gifts from table given a user ID.""" gift = get_gifts_by_user_id( user_id ) schema = GiftSchema( many=True ) result = schema.dump( gift ).data return result, status.HTTP_200_OK
def test_get_gift_update_note(self): """Gifts endpoint to get a list of notes given a gift_searchable_id ( methods = [ GET ] ).""" with self.app.app_context(): # Parameter in URL is for a searchable_id_prefix. url = '/donation/gift/{}/notes' # Create 3 gifts to attach transactions to. # Create each gift with a reproducible UUID. total_gifts = 4 searchable_ids = get_gift_searchable_ids() gift_models = [] for i in range(0, total_gifts): # pylint: disable=W0612 gift_json = get_gift_dict() del gift_json['id'] gift_json['searchable_id'] = searchable_ids[i] gift_model = GiftSchema().load(gift_json).data gift_models.append(gift_model) database.session.bulk_save_objects(gift_models) database.session.commit() # Create 3 transactions attached to the same gift ID = 1 and one transaction to each remaining gift. total_transactions = 5 transaction_models = [] for i in range(0, total_transactions): # pylint: disable=W0612 transaction_json = get_transaction_dict() del transaction_json['id'] if i <= 2: transaction_json['gift_id'] = 1 transaction_json['gift_searchable_id'] = uuid.UUID( searchable_ids[0]).hex elif i == 3: transaction_json['gift_id'] = i transaction_json['gift_searchable_id'] = uuid.UUID( searchable_ids[i]).hex transaction_json['notes'] = '' else: transaction_json['gift_id'] = i transaction_json['gift_searchable_id'] = uuid.UUID( searchable_ids[i]).hex transaction_model = TransactionSchema().load( transaction_json).data transaction_models.append(transaction_model) database.session.bulk_save_objects(transaction_models) database.session.commit() # searchable_ids[ 0 ] is gift ID = 1 and will have 3 transactions. response = self.test_client.get(url.format( uuid.UUID(searchable_ids[1]).hex), headers=self.headers) self.assertEqual(len(json.loads(response.data.decode('utf-8'))), 0) # searchable_ids[ 1 ] is gift ID = 2 and will have 0 transactions. response = self.test_client.get(url.format( uuid.UUID(searchable_ids[1]).hex), headers=self.headers) self.assertEqual(len(json.loads(response.data.decode('utf-8'))), 0) # searchable_ids[ 2 ] is gift ID = 3 and will have 1 transaction with note = ''. response = self.test_client.get(url.format( uuid.UUID(searchable_ids[2]).hex), headers=self.headers) self.assertEqual(len(json.loads(response.data.decode('utf-8'))), 0) # searchable_ids[ 3 ] is gift ID = 4 and will have 1 transaction with a note != ''. response = self.test_client.get(url.format( uuid.UUID(searchable_ids[3]).hex), headers=self.headers) self.assertEqual(len(json.loads(response.data.decode('utf-8'))), 1)
def test_braintree_webhooks(self, mock_init_gateway_function, mock_subscription_function, get_ultsys_user_function): # pylint: disable=unused-argument """Make sure the webhook endpoint receives a payload and makes updates as expected.""" with self.app.app_context(): url = '/donation/webhook/braintree/subscription' # Create the sourced by agent for the subscription webhook. agent_model = from_json(AgentSchema(), get_agent_jsons()[0], create=True) database.session.add(agent_model.data) database.session.commit() # Here is the first gift as check. gift_dict = get_gift_dict({ 'user_id': 1, 'method_used': METHOD_USED, 'sourced_from_agent_id': 1, 'recurring_subscription_id': 'recurring_subscription_id' }) gift_model = from_json(GiftSchema(), gift_dict, create=True) database.session.add(gift_model.data) database.session.flush() # Create a transaction on the gift. transaction_dict = get_transaction_dict({ 'gift_id': gift_model.data.id, 'enacted_by_agent_id': agent_model.data.id, 'type': 'Gift', 'gross_gift_amount': Decimal('1.00') }) transaction_model = from_json(TransactionSchema(), transaction_dict, create=True) database.session.add(transaction_model.data) database.session.commit() # Here is the fake POST from Braintree when the subscription webhook is triggered. response = self.test_client.post( url, data={ 'bt_signature': 'bt_signature', 'bt_payload': 'subscription_charged_successfully' }) self.assertEqual(response.status_code, status.HTTP_200_OK) method_used_id = MethodUsedModel.get_method_used( 'name', METHOD_USED).id gift = GiftModel.query.filter_by(id=1).one_or_none() self.assertEqual(gift.method_used_id, method_used_id) self.assertEqual(gift.sourced_from_agent_id, SOURCED_FROM_AGENT) self.assertEqual(gift.recurring_subscription_id, RECURRING_SUBSCRIPTION_ID) transaction = TransactionModel.query.filter_by(id=1).one_or_none() self.assertEqual(transaction.gift_id, 1) self.assertEqual(transaction.type, 'Gift') self.assertEqual(transaction.status, 'Completed') response = self.test_client.post( url, data={ 'bt_signature': 'bt_signature', 'bt_payload': 'subscription_charged_unsuccessfully' }) self.assertEqual(response.status_code, status.HTTP_200_OK) transaction = TransactionModel.query.filter_by(id=3).one_or_none() self.assertEqual(transaction.status, 'Declined') response = self.test_client.post(url, data={ 'bt_signature': 'bt_signature', 'bt_payload': 'subscription_went_past_due' }) self.assertEqual(response.status_code, status.HTTP_200_OK) transaction = TransactionModel.query.filter_by(id=4).one_or_none() self.assertEqual(transaction.status, 'Failed') response = self.test_client.post(url, data={ 'bt_signature': 'bt_signature', 'bt_payload': 'subscription_expired' }) self.assertEqual(response.status_code, status.HTTP_200_OK) transaction = TransactionModel.query.filter_by(id=5).one_or_none() self.assertEqual(transaction.status, 'Failed')
def manage_recurring_sales( sale_id, sale, history_attributes, priority_sale_data ): """Logic for one item in the loop over sales for new statuses when they are recurring. :param sale_id: The key of the loop, the Braintree sale ID. :param sale: The value for the loop, a Braintree sale. :param history_attributes: The history attributes for the sale. :return: """ try: # Try to get the user ID from previous gifts. gifts_with_subscription_id = GiftModel.query.filter_by( recurring_subscription_id=sale.subscription_id ).all() user_id = None for gift in gifts_with_subscription_id: if gift.user_id and gift.user_id != 999999999: user_id = gift.user_id if not user_id: user_id = 999999999 if user_id == 999999999: priority_sale_data.append( get_row_of_data( sale, BRAINTREE_SALE_FIELDS, 'Recurring transaction without an initial transaction in database.' ) ) else: try: # This is a subscription and needs its own gift if not already present. transaction_initial = TransactionModel.query.filter_by( reference_number=sale_id ) \ .filter_by( type='Gift' ) \ .filter_by( status='Completed' ).one_or_none() if not transaction_initial: gift_dict = { 'id': None, 'searchable_id': uuid.uuid4(), 'user_id': user_id, 'customer_id': sale.customer[ 'id' ], 'method_used': 'Admin-Entered Credit Card', 'sourced_from_agent_id': AGENT_ID, 'given_to': MERCHANT_ACCOUNT_ID[ sale.merchant_account_id ], 'recurring_subscription_id': sale.subscription_id } gift_model = from_json( GiftSchema(), gift_dict ) database.session.add( gift_model.data ) database.session.flush() database.session.commit() gift_id = gift_model.data.id else: gift_id = transaction_initial.gift_id transaction_models = build_transactions( sale, history_attributes, gift_id, sale.refunded_transaction_id ) database.session.bulk_save_objects( transaction_models ) except: # noqa: E722 database.session.rollback() logging.debug( UpdaterCriticalPathError( where='manage_recurring_sales rolling back', type_id=sale_id ).message ) except: # noqa: E722 logging.debug( UpdaterCriticalPathError( where='manage_recurring_sales', type_id=sale_id ).message )
def valid_paypal_transaction(row, enacted_by_agent_id, agent_emails, ids, bulk_objects): """Build the valid PayPal gifts/transactions. :param row: A row of CSV data. :param enacted_by_agent_id: The admin user making the download of PayPal data.. :param agent_emails: The agent emails needed here. :param ids: The collected transaction and unresolved transaction IDs. :param bulk_objects: The lists for bulk saving. :return: """ # This is a gift. try: # The returned Ultsys user object is something like: { 'ID': -999 }. user = get_ultsys_user({ 'email': { 'eq': row['from_email_address'] } }).json()[0] except (AttributeError, IndexError, KeyError): user = None gift_payload_user_id = user['ID'] if user else -1 given_to = determine_given_to(row) gift_payload = { 'user_id': gift_payload_user_id, 'method_used': 'Admin-Entered Credit Card', 'sourced_from_agent_id': agent_emails.get(row['to_email_address']), 'given_to': given_to } # Subscription Number is optional. subscription_number = row.get('subscription_number') if subscription_number and subscription_number != 'NA': gift_payload['recurring_subscription_id'] = subscription_number gift_schema = from_json(GiftSchema(), gift_payload) gift_model = gift_schema.data # Need Gift ID to associate with transaction. Need to add and flush here --> slow. # Anyone know how to do it better? database.session.add(gift_model) database.session.flush() transaction_model = generate_a_transaction(row, ids['transaction'], { 'agent_id': enacted_by_agent_id, 'type': 'Gift', 'gift_id': gift_model.id }) if user and given_to == 'TBD': transaction_model.notes = 'Can not determine whether the gift belongs to ACTION or NERF with user_id: {}'\ .format( user[ 'ID' ] ) bulk_objects['transaction'].append(transaction_model) # Caging if do not know user or given_to if gift_payload_user_id == -1 or given_to == 'TBD': caged_donor_model = generate_caged_donor(row) caged_donor_model.gift_id = gift_model.id caged_donor_model.gift_searchable_id = gift_model.searchable_id bulk_objects['caged_donor'].append(caged_donor_model) ids['transaction'][row['transaction_id']] = gift_model.id
def test_get_gifts_without_id(self): """Gifts-transaction endpoint with gift ID's retrieves all transactions on gifts ( methods = [ GET ] ).""" with self.app.app_context(): url = '/donation/gifts/transactions' # Create some gifts to retrieve. total_gifts = 5 gift_models = create_model_list(GiftSchema(), get_gift_dict(), total_gifts) # Create 2 sets of transactions, each attached to a separate gift. total_transactions = 5 transaction_gift_ids = [2, 4] transaction_models_1 = create_model_list( TransactionSchema(), get_transaction_dict({'gift_id': transaction_gift_ids[0]}), total_transactions) transaction_models_2 = create_model_list( TransactionSchema(), get_transaction_dict({'gift_id': transaction_gift_ids[1]}), total_transactions) database.session.bulk_save_objects(gift_models) database.session.bulk_save_objects(transaction_models_1) database.session.bulk_save_objects(transaction_models_2) database.session.commit() gift_2 = GiftModel.query.filter_by(id=2).one_or_none() searchable_id_2 = gift_2.searchable_id gift_4 = GiftModel.query.filter_by(id=4).one_or_none() searchable_id_4 = gift_4.searchable_id # Ensure a GET returns all transactions. response = self.test_client.get(url, headers=self.headers) data_returned = json.loads(response.data.decode('utf-8')) self.assertEqual(len(data_returned), 2 * total_transactions) # Ensure GET retrieves all transactions attached to the specified gift and the ID is correct. response = self.test_client.post( url, data=json.dumps({'searchable_ids': [str(searchable_id_2)]}), content_type='application/json', headers=self.headers) data_returned = json.loads(response.data.decode('utf-8')) self.assertEqual(len(data_returned), 5) self.assertEqual(data_returned[0]['gift_searchable_id'], str(searchable_id_2)) # Ensure GET retrieves all transactions attached to the 2 gifts. response = self.test_client.post( url, data=json.dumps({ 'searchable_ids': [str(searchable_id_2), str(searchable_id_4)] }), content_type='application/json', headers=self.headers) data_returned = json.loads(response.data.decode('utf-8')) self.assertEqual(len(data_returned), 10)
def test_get_gifts_by_date_past(self): """Gifts endpoint which retrieves all gifts newer than date, or between 2 dates ( methods = [ POST ] ).""" with self.app.app_context(): url = '/donation/gifts/date' # To create each gift with a new UUID call get_gift_dict() separately. totals = {'gifts': 2, 'transactions': 4} gift_models = [] for i in range(0, totals['gifts']): # pylint: disable=W0612 gift_json = get_gift_dict() del gift_json['id'] gift_json['searchable_id'] = uuid.uuid4() gift_model = GiftSchema().load(gift_json).data gift_models.append(gift_model) database.session.bulk_save_objects(gift_models) database.session.commit() # Create a set of transactions and attach to a specific gift. # Here are the time deltas: { gift 1: [ 0, -2, -4, -6 ], gift 2: [ -8, -10, -12, -14 ] } transaction_models = create_gift_transactions_date( TransactionSchema(), get_transaction_dict(), totals['transactions'], totals['gifts']) database.session.bulk_save_objects(transaction_models) database.session.commit() date_in_utc_now = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0) # Date in the past on only gift 1. date_in_utc = date_in_utc_now - timedelta(days=2) response = self.test_client.post( url, data=json.dumps({'date': date_in_utc.strftime('%Y-%m-%d')}), content_type='application/json', headers=self.headers) data_returned = json.loads(response.data.decode('utf-8')) self.assertEqual(len(data_returned), 1) self.assertEqual(data_returned[0]['searchable_id'], str(gift_models[0].searchable_id)) # Date in the past which includes transactions on both gift 1 and gift 2. date_in_utc = date_in_utc_now - timedelta(days=10) response = self.test_client.post( url, data=json.dumps({'date': date_in_utc.strftime('%Y-%m-%d')}), content_type='application/json', headers=self.headers) data_returned = json.loads(response.data.decode('utf-8')) self.assertEqual(len(data_returned), 2) self.assertEqual(data_returned[0]['searchable_id'], str(gift_models[0].searchable_id)) self.assertEqual(data_returned[1]['searchable_id'], str(gift_models[1].searchable_id)) # Date range in the past, which includes transactions on both gift 1 and 2. date_in_utc_0 = date_in_utc_now - timedelta(days=6) date_in_utc_1 = date_in_utc_now - timedelta(days=8) response = self.test_client.post( url, data=json.dumps({ 'date': [ date_in_utc_0.strftime('%Y-%m-%d'), date_in_utc_1.strftime('%Y-%m-%d') ] }), content_type='application/json', headers=self.headers) data_returned = json.loads(response.data.decode('utf-8')) self.assertEqual(len(data_returned), 2) self.assertEqual(data_returned[0]['searchable_id'], str(gift_models[0].searchable_id)) self.assertEqual(data_returned[1]['searchable_id'], str(gift_models[1].searchable_id)) # Date in the distant past, should bring back no results. date_in_utc = date_in_utc_now - timedelta(days=16) response = self.test_client.post( url, data=json.dumps({'date': date_in_utc.strftime('%Y-%m-%d')}), content_type='application/json', headers=self.headers) data_returned = json.loads(response.data.decode('utf-8')) self.assertEqual(len(data_returned), 2) # Date in the future, should bring back no results. date_in_utc = date_in_utc_now + timedelta(days=16) response = self.test_client.post( url, data=json.dumps({'date': date_in_utc.strftime('%Y-%m-%d')}), content_type='application/json', headers=self.headers) data_returned = json.loads(response.data.decode('utf-8')) self.assertEqual(len(data_returned), 0)